/* ==========================================================================
   Neelam Dhiman | Portfolio
   style.css  —  Light / Cream theme
   --------------------------------------------------------------------------
   01. Theme variables
   02. Reset & base
   03. Layout (container / section / headings)
   04. Buttons
   05. Scrollbar & selection
   06. Reveal animations
   07. Animated background (canvas + grid texture)
   08. Navbar (+ mobile menu)
   09. Custom cursor
   10. Footer
   11. About section
   12. Skills section
   13. Projects section
   14. Vision section
   15. FAQ section
   16. Contact section
   17. Footer links + back to top
   18. Responsive tweaks
   19. Reduced motion
   ========================================================================== */


/* ==========================================================================
   01. THEME VARIABLES
   ========================================================================== */
:root {
  --bg: #FAF5EA;                            /* main cream background   */
  --bg-alt: #F2EAD8;                        /* slightly darker cream   */
  --surface: rgba(255, 253, 248, 0.75);     /* frosted card surface    */
  --text: #2A2520;                          /* dark charcoal text      */
  --muted: #6B6358;                         /* secondary text          */
  /* Body copy on the lighter "My Story" card. --muted only reached 5.7:1
     there and read as washed out; this is 9.5:1 — still soft, far clearer. */
  --text-soft: #4A4239;
  --border: rgba(42, 37, 32, 0.14);        /* hairline borders        */
  --accent: #a8510e;                        /* warm brown — main accent   */
  --accent-soft: rgba(139, 94, 60, 0.12);  /* soft brown wash            */
  --glow: rgba(139, 94, 60, 0.35);         /* accent glow / shadow       */
  --accent-2: #E07A3F;                      /* warm orange (decorative)   */

  /* Contrast-safe orange. --accent-2 only reaches 2.75:1 on the cream
     background, which is below the WCAG AA minimum of 4.5:1 for small text.
     Use this for the form error messages and error borders; keep --accent-2
     for decorative fills only. */
  --accent-2-text: #B4521A;                 /* 4.64:1 on --bg, 4.93:1 on cards */

  /* Same colour as --accent, in "R, G, B" form so it can be used as
     rgba(var(--accent-rgb), 0.06) for glows, patterns and particles.
     Change --accent and update this one value to re-tint the whole site. */
  --accent-rgb: 168, 81, 14;

  /* Typography */
  /* Typography
     Poppins is the site font (loaded in index.html). Elephant is a display
     serif used only for the hero title — change this one line to re-theme. */
  --font-heading: "Poppins", system-ui, sans-serif;
  --font-body: "Poppins", system-ui, -apple-system, sans-serif;
  --font-display: "Elephant", "Poppins", Georgia, serif;
  /* Italic serif for the rotating line under the hero name. */
  --font-script: "Playfair Display", "Elephant", Georgia, serif;

  /* Layout */
  --container-width: 1200px;
  --nav-height: 78px;
  --nav-height-small: 64px;

  /* Motion */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}


/* ==========================================================================
   02. RESET & BASE
   ========================================================================== */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;          /* smooth anchor scrolling       */
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100vh;
  background-color: var(--bg);
  /* Faint dotted-grid texture over the cream background */
  background-image: radial-gradient(rgba(42, 37, 32, 0.055) 1px, transparent 1px);
  background-size: 26px 26px;
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.7;
  overflow-x: hidden;
}

/* Keeps the fixed background from covering content */
body,
main,
section,
footer {
  position: relative;
  z-index: 1;
}

img,
svg,
video,
canvas {
  display: block;
  max-width: 100%;
}

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

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

ul,
ol {
  list-style: none;
}

/* Visually hidden but still read by screen readers.
   Used so fast-changing text (like the hero typing effect) is announced
   once as a full sentence instead of letter by letter. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.01em;
}


/* ==========================================================================
   03. LAYOUT
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: 24px;
}

.section {
  padding: 100px 0;
  /* Keeps the section title clear of the fixed navbar when jumping to it.
     + 2px covers the navbar's 1px bottom border, which sits outside the
     --nav-height box. */
  scroll-margin-top: calc(var(--nav-height) + 2px);
}

/* Centred, large, bold section headings */
.section-heading {
  font-family: var(--font-heading);
  font-size: clamp(1.9rem, 4vw, 2.9rem);
  font-weight: 800;
  text-align: center;
  letter-spacing: -0.015em;
}

.section-subheading {
  max-width: 640px;
  margin: 16px auto 0;
  color: var(--muted);
  text-align: center;
}

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


/* ==========================================================================
   04. BUTTONS
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 26px;
  border: 1px solid var(--text);
  border-radius: 8px;
  font-family: var(--font-heading);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text);
  background: transparent;
  transition:
    background-color 0.3s var(--ease-out),
    color 0.3s var(--ease-out),
    border-color 0.3s var(--ease-out),
    box-shadow 0.3s var(--ease-out);
}

/* Arrow: added automatically, so any <a class="btn"> gets it for free.
   Delete this block if you prefer to write <span class="btn-arrow">&rarr;</span>
   yourself inside each button. */
.btn::after {
  content: "\2192";
  display: inline-block;
  transition: transform 0.3s var(--ease-out);
}

.btn .btn-arrow {
  display: inline-block;
  transition: transform 0.3s var(--ease-out);
}

/* Hover: fills teal, text turns white, soft glow, arrow slides right */
.btn:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 8px 26px var(--glow);
}

.btn:hover::after {
  transform: translateX(5px);
}

.btn:hover .btn-arrow {
  transform: translateX(5px);
}

.btn:active {
  transform: translateY(1px);
}

/* --- Button variants used by the project cards ---
   .btn itself is already the outlined look, so .btn-secondary only needs to
   state that intent. .btn-primary is the filled, accent-coloured one. */
.btn-secondary {
  background-color: transparent;
  border-color: var(--text);
  color: var(--text);
}

.btn-primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}

/* A filled button is already in its hover colour, so hovering must not try to
   fill it again — just lift it with the usual glow and nudge the arrow. */
.btn-primary:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 8px 26px var(--glow);
}

/* The global focus ring is accent-on-cream. On a filled accent button that
   would be invisible, so this one gets a dark ring against the fill and a
   light halo outside it. */
.btn-primary:focus-visible {
  outline: 3px solid var(--text);
  outline-offset: 3px;
  box-shadow: 0 0 0 6px var(--bg);
}


/* ==========================================================================
   05. SCROLLBAR & SELECTION
   ========================================================================== */
::-webkit-scrollbar {
  width: 9px;
}

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

::-webkit-scrollbar-thumb {
  background: var(--accent);
  border-radius: 99px;
}

::-webkit-scrollbar-thumb:hover {
  background: #0A6F69;
}

html {
  scrollbar-color: var(--accent) var(--bg);
  scrollbar-width: thin;
}

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

/* ==========================================================================
   02b. VISIBLE KEYBOARD FOCUS
   Every interactive element gets a clearly visible ring when focused by
   keyboard. :focus-visible means clicking with a mouse does NOT show it,
   so it stays out of the way for pointer users.
   ========================================================================== */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Round things keep round rings */
.nav-link:focus-visible,
.footer-link:focus-visible,
.social-link:focus-visible,
.carousel-btn:focus-visible,
.carousel-dot:focus-visible,
.to-top:focus-visible,
.hero-scroll:focus-visible {
  outline-offset: 4px;
  border-radius: 99px;
}

.logo:focus-visible,
.carousel-thumb:focus-visible {
  border-radius: 8px;
}

/* Inputs get a soft glow instead of a hard outline */
.form-input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent), 0 0 0 6px var(--accent-soft);
}


/* ==========================================================================
   06. REVEAL ON SCROLL
   ========================================================================== */
.reveal {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
}

.reveal.in-view {
  opacity: 1;
  transform: translateY(0);
}

/* Variant: also scales in (for grid items such as the skills icons).
   Declared after .reveal so it wins the specificity tie. */
.reveal-scale {
  transform: translateY(30px) scale(0.9);
}

.reveal-scale.in-view {
  transform: translateY(0) scale(1);
}

/* Staggered delays */
.reveal-delay-1 { transition-delay: 0.1s; }
.reveal-delay-2 { transition-delay: 0.2s; }
.reveal-delay-3 { transition-delay: 0.3s; }
.reveal-delay-4 { transition-delay: 0.4s; }


/* ==========================================================================
   07. ANIMATED BACKGROUND
   --------------------------------------------------------------------------
   The particle canvas was removed - the drifting background dots were not
   wanted. There is no #bg-canvas rule left, and script.js no longer creates a
   canvas. The subtle dotted-grid texture on `body` (section 02) is a separate,
   much fainter thing and is still in place.

   Scroll progress bar (thin, at the very top) ---
   JS sets transform: scaleX(0 → 1). transform-origin is on the left so it
   grows from the start of the page. Using transform means no layout/paint
   work on every scroll tick. */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 300;              /* above the navbar (z-index 100) */
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  pointer-events: none;
  will-change: transform;
}

/* Respect users who ask for less motion: show a plain static bar */
@media (prefers-reduced-motion: reduce) {
  .scroll-progress {
    transition: none;
  }
}


/* ==========================================================================
   08. NAVBAR
   ========================================================================== */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition:
    background-color 0.35s var(--ease-out),
    backdrop-filter 0.35s var(--ease-out),
    border-color 0.35s var(--ease-out),
    box-shadow 0.35s var(--ease-out);
}

.navbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--nav-height);
  transition: height 0.35s var(--ease-out);
}

/* Scrolled state: blurred cream, thin border, slightly smaller */
.navbar.scrolled {
  background-color: rgba(250, 245, 234, 0.78);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--border);
  box-shadow: 0 6px 24px rgba(42, 37, 32, 0.05);
}

.navbar.scrolled .navbar-inner {
  height: var(--nav-height-small);
}

/* Logo: "NEELAM DHIMAN" with a small "PORTFOLIO" label underneath */
.logo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  line-height: 1;
  color: var(--text);
}

.logo-name {
  font-family: var(--font-heading);
  font-size: 1.12rem;
  font-weight: 800;
  letter-spacing: 0.07em;
}

.logo-sub {
  font-family: var(--font-body);
  font-size: 0.56rem;
  font-weight: 600;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--accent);
  padding-left: 1px;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 30px;
}

.nav-link {
  position: relative;
  padding: 4px 0;
  font-family: var(--font-heading);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--muted);
  transition: color 0.25s var(--ease-out);
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 2px;
  background-color: var(--accent);
  transition: width 0.3s var(--ease-out);
}

.nav-link:hover {
  color: var(--text);
}

/* Active section link */
.nav-link.active {
  color: var(--accent);
}

.nav-link.active::after {
  width: 100%;
}

/* Navbar contact button — slightly slimmer than a normal .btn, no arrow */
.btn-nav {
  padding: 9px 20px;
  font-size: 0.78rem;
}

.btn-nav::after {
  content: none;
}

/* Navbar "Hire Me" pill — filled accent so it stands out from CONTACT */
.btn-nav-hire {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  border-radius: 999px;
}

.btn-nav-hire:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 6px 20px var(--glow);
}

/* --- Hamburger --- */
.hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 42px;
  height: 42px;
  padding: 0 9px;
}

.hamburger span {
  display: block;
  width: 100%;
  height: 2px;
  background-color: var(--text);
  border-radius: 2px;
  transition: transform 0.3s var(--ease-out), opacity 0.2s var(--ease-out);
}

/* Hamburger → X */
.hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger.open span:nth-child(2) { opacity: 0; }
.hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --- Full-screen cream overlay menu (mobile) --- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  background-color: var(--bg);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s var(--ease-out), visibility 0.35s;
}

.mobile-menu.open {
  opacity: 1;
  visibility: visible;
}

.mobile-link {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--text);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
}

.mobile-menu.open .mobile-link {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger the overlay links in */
.mobile-menu.open .mobile-link:nth-child(1) { transition-delay: 0.06s; }
.mobile-menu.open .mobile-link:nth-child(2) { transition-delay: 0.12s; }
.mobile-menu.open .mobile-link:nth-child(3) { transition-delay: 0.18s; }
.mobile-menu.open .mobile-link:nth-child(4) { transition-delay: 0.24s; }
.mobile-menu.open .mobile-link:nth-child(5) { transition-delay: 0.30s; }
.mobile-menu.open .mobile-link:nth-child(6) { transition-delay: 0.36s; }

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


/* ==========================================================================
   09. CUSTOM CURSOR (desktop only)
   ========================================================================== */
.cursor-dot,
.cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  border-radius: 50%;
  pointer-events: none;
  display: none;              /* shown by JS only on fine-pointer devices */
}

/* 6px teal dot — follows instantly */
.cursor-dot {
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  background-color: var(--accent);
  box-shadow: 0 0 8px var(--glow);
  transition: opacity 0.25s var(--ease-out);
}

/* 32px teal ring — follows with smooth lag */
.cursor-ring {
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  border: 1.5px solid var(--accent);
  opacity: 0.65;
  transition:
    width 0.3s var(--ease-out),
    height 0.3s var(--ease-out),
    margin 0.3s var(--ease-out),
    background-color 0.3s var(--ease-out),
    border-color 0.3s var(--ease-out),
    opacity 0.3s var(--ease-out);
}

/* Grows with a soft fill over links / buttons / cards */
.cursor-ring.hovering {
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  background-color: var(--accent-soft);
  border-color: var(--accent);
  opacity: 1;
}

body.has-custom-cursor,
body.has-custom-cursor * {
  cursor: none !important;
}


/* ==========================================================================
   10. FOOTER
   ========================================================================== */
.footer {
  padding: 64px 0 32px;
  border-top: 1px solid var(--border);
  background-color: var(--bg-alt);
  color: var(--muted);
  font-size: 0.88rem;
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1.2fr 1.2fr 1.5fr;
  gap: 48px;
  margin-bottom: 48px;
}

.footer-brand {
  max-width: 320px;
}

.footer-logo {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  line-height: 1;
  color: var(--text);
  text-decoration: none;
  margin-bottom: 16px;
}

.footer-logo .logo-name {
  font-family: var(--font-heading);
  font-size: 1.12rem;
  font-weight: 800;
  letter-spacing: 0.07em;
}

.footer-logo .logo-sub {
  font-family: var(--font-body);
  font-size: 0.56rem;
  font-weight: 600;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--accent);
  padding-left: 1px;
}

.footer-tagline {
  color: var(--muted);
  line-height: 1.7;
  margin-bottom: 20px;
  font-size: 0.9rem;
}

.footer-social {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.footer-social .social-link {
  width: 60px;
  gap: 6px;
}

.footer-social .social-link__icon {
  width: 40px;
  height: 40px;
}

.footer-social .social-link__glyph {
  width: 17px;
  height: 17px;
}

.footer-social .social-link__label {
  font-size: 0.66rem;
}

.footer-nav {
  display: flex;
  flex-direction: column;
}

.footer-heading {
  font-family: var(--font-heading);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text);
  margin-bottom: 20px;
}

.footer-link-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.footer-link-list .footer-link {
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
  position: relative;
}

.footer-link-list .footer-link::before {
  content: "";
  position: absolute;
  left: -14px;
  top: 50%;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0;
  transform: translateY(-50%) scale(0);
  transition: opacity 0.2s, transform 0.2s;
}

.footer-link-list .footer-link:hover {
  color: var(--accent);
  padding-left: 14px;
}

.footer-link-list .footer-link:hover::before {
  opacity: 1;
  transform: translateY(-50%) scale(1);
}

.footer-contact {
  display: flex;
  flex-direction: column;
}

.footer-address {
  font-style: normal;
  line-height: 2.2;
  color: var(--muted);
}

.footer-address p {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.9rem;
}

.footer-icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  stroke-width: 2.2;
  color: var(--accent);
}

.footer-address .footer-link {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.footer-address .footer-link:hover {
  color: var(--accent);
}

.footer-bottom {
  padding-top: 24px;
  border-top: 1px solid var(--border);
  text-align: center;
  color: var(--muted);
  font-size: 0.82rem;
}

/* Responsive footer */
@media (max-width: 1024px) {
  .footer-grid {
    grid-template-columns: 1fr 1fr;
    gap: 40px;
  }
  .footer-brand {
    grid-column: span 2;
    max-width: none;
  }
}

@media (max-width: 640px) {
  .footer-grid {
    grid-template-columns: 1fr;
    gap: 36px;
  }
  .footer-brand {
    grid-column: auto;
  }
  .footer-social {
    justify-content: flex-start;
  }
}


/* ==========================================================================
   11. ABOUT SECTION
   --------------------------------------------------------------------------
   .about-intro      → 2 columns: text (60%) + photo (40%)
   .about-story      → full-width "My Story" card with drawing corner lines
   ========================================================================== */

/* --- Part 1: layout ---
   The photo sits on the LEFT and the text on the RIGHT. The markup still
   lists the text first (so screen readers read it first); `order` flips the
   visual side. */
.about-intro {
  display: grid;
  grid-template-columns: 1fr 1.5fr;   /* ≈40% photo / 60% text */
  gap: 64px;
  align-items: center;
  /* The columns wait off to the side before sliding in. That waiting state
     reaches past the right edge of the container, which is what created a
     horizontal scrollbar across the whole page. Clipping just the intro
     keeps the "My Story" card's corner lines (a sibling, further down)
     from being cut off. Vertical overflow is untouched. */
  overflow-x: clip;
}

.about-intro__photo {
  order: -1;                 /* photo on the left on desktop */
  display: flex;
  justify-content: center;
}

/* --- Part 1: text column --- */
.about-subtitle {
  margin-top: 20px;
  font-size: clamp(1.1rem, 2vw, 1.32rem);
  font-weight: 700;
  letter-spacing: -0.005em;
}

.about-paragraph {
  margin-top: 18px;
  color: var(--muted);
}

/* Bold brown highlight inside a paragraph */
.about-paragraph strong {
  color: var(--accent);
  font-weight: 600;
}

/* --- Part 1: PORTRAIT photo (3:4 rounded rectangle, not a circle) --- */
.about-photo {
  position: relative;
  width: 320px;
  max-width: 100%;
  /* Portrait 3:4, matching the photo's own proportions so nothing is
     cropped away. */
  aspect-ratio: 3 / 4;
  border: 2px solid var(--accent);
  border-radius: 26px;
  overflow: hidden;
  background-color: var(--bg-alt);
  /* Soft brown halo ring + drop shadow */
  box-shadow:
    0 0 0 10px var(--accent-soft),
    0 24px 60px var(--glow);

  /* Animation: a slow up/down float (uses `transform`). The old breathing
     glow animated box-shadow (a per-frame repaint) and was removed; the
     static halo below still gives the photo its soft edge. */
  animation: about-float 7s ease-in-out infinite;

  /* `scale` is a separate property from `transform`, so the hover grow
     works alongside the float animation instead of fighting it. */
  scale: 1;
  transition: scale 0.5s var(--ease-out);
}

/* --- Hover: grow --- */
.about-intro__photo:hover .about-photo {
  scale: 1.045;
}

.about-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 20%;
  transition: scale 0.6s var(--ease-out);
}

/* A gentle push-in on the picture itself, on top of the frame growing */
.about-intro__photo:hover .about-photo img {
  scale: 1.05;
}

/* Monogram shown instead of a broken image (see script.js) */
.about-photo__initials {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 4.4rem;
  font-weight: 800;
  color: var(--accent);
}

.about-photo.is-empty img {
  display: none;
}

.about-photo.is-empty .about-photo__initials {
  display: flex;
}

/* Slow "breathing" glow for the About photo. Speeds up on hover. */
@keyframes about-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-14px); }
}

/* ==========================================================================
   SCROLL-DIRECTION AWARE INTRO
   --------------------------------------------------------------------------
   The photo and the text block slide in from OPPOSITE sides, and which
   side depends on the way you are scrolling:

     scrolling DOWN from the hero  -> photo enters from the left,
                                      text enters from the right
     scrolling UP from "My Story" -> photo enters from the right,
                                      text enters from the left

   script.js writes data-scroll-dir="down" | "up" on the .about-intro
   element, then toggles .in-view each time the block enters the screen.
   The .in-view rules come LAST so they win the specificity tie.
   ========================================================================== */
.about-intro__text,
.about-intro__photo {
  opacity: 0;
  transition:
    opacity 0.9s var(--ease-out),
    transform 0.9s var(--ease-out);
}

/* --- Entering while scrolling DOWN (came from the hero above) --- */
.about-intro[data-scroll-dir="down"] .about-intro__photo { transform: translateX(-64px); }
.about-intro[data-scroll-dir="down"] .about-intro__text  { transform: translateX(64px); }

/* --- Entering while scrolling UP (came from "My Story" below) --- */
.about-intro[data-scroll-dir="up"] .about-intro__photo { transform: translateX(64px); }
.about-intro[data-scroll-dir="up"] .about-intro__text  { transform: translateX(-64px); }

/* Before JS sets a direction (or if it never runs) the block still slides
   in from below, so nothing is ever left stranded invisible. */
.about-intro:not([data-scroll-dir]) .about-intro__photo { transform: translateY(40px); }
.about-intro:not([data-scroll-dir]) .about-intro__text  { transform: translateY(40px); }

/* --- The "in place" state. Declared after the rules above on purpose. --- */
.about-intro.in-view .about-intro__photo,
.about-intro.in-view .about-intro__text {
  opacity: 1;
  transform: translateX(0) translateY(0);
}

/* --- The individual text elements fade in one after another.
       They no longer move on their own; the column is what slides. --- */
.about-intro__text > * {
  opacity: 0;
  transition: opacity 0.7s var(--ease-out);
}

.about-intro.in-view .about-intro__text > * { opacity: 1; }

.about-intro__text > *:nth-child(1) { transition-delay: 0.10s; }
.about-intro__text > *:nth-child(2) { transition-delay: 0.20s; }
.about-intro__text > *:nth-child(3) { transition-delay: 0.30s; }
.about-intro__text > *:nth-child(4) { transition-delay: 0.40s; }
.about-intro__text > *:nth-child(5) { transition-delay: 0.50s; }
.about-intro__text > *:nth-child(6) { transition-delay: 0.60s; }

/* Section headings are centred by default. In this section the text lives in
   a right-hand column next to the photo, so centring it looked disconnected
   from the left-aligned paragraphs underneath. */
.about-intro__text > .section-heading {
  text-align: left;
}

/* --- Part 2: "My Story" card ---
   Background is a soft diagonal gradient that sits BETWEEN the page cream
   and pure white: a touch lighter than the page, but still warm rather
   than a flat white box. Edit the two hex values to re-tone it. */
.about-story {
  position: relative;
  margin-top: 80px;
  padding: 50px 54px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background-image: linear-gradient(160deg, #FEFCF7 0%, #F7F1E4 100%);
  box-shadow: 0 16px 44px rgba(42, 37, 32, 0.07);
  /* Centres the title, which is shrink-wrapped below so its left bar sits
     right against the words instead of stranded at the far left edge. */
  text-align: center;

  /* Direction-aware entrance, same idea as the intro above.
     Default (scrolling DOWN, so the card is below you) rises from below. */
  opacity: 0;
  transform: translateY(52px);
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
}

/* Scrolling UP, so the card is above you and you approached it from below:
   it comes down into place instead. */
.about-story[data-scroll-dir="up"] {
  transform: translateY(-52px);
}

/* Declared after the rule above so it wins the specificity tie. */
.about-story.in-view {
  opacity: 1;
  transform: translateY(0);
}

/* Title with a thin brown vertical bar on its left.
   `inline-block` shrink-wraps it to the words, so the bar hugs the text
   while the card's `text-align: center` keeps the whole thing centred. */
.about-story__title {
  display: inline-block;
  padding-left: 18px;
  border-left: 3px solid var(--accent);
  font-size: clamp(1.7rem, 2.7vw, 2.35rem);
  font-weight: 800;
  letter-spacing: -0.015em;
  text-align: left;
}

.about-story__body {
  max-width: 820px;
  margin: 26px auto 0;
  /* Darker than --muted. The old muted grey was hard to read on the
     lighter card background; this is a comfortable 9.5:1. */
  color: var(--text-soft);
  font-size: 1.04rem;
  line-height: 1.85;
  text-align: center;
}

.about-story__body p + p {
  margin-top: 18px;
}

/* Title and paragraphs fade in one after another once the card has arrived. */
.about-story__title,
.about-story__body > * {
  opacity: 0;
  transition: opacity 0.7s var(--ease-out);
}

.about-story.in-view .about-story__title,
.about-story.in-view .about-story__body > * {
  opacity: 1;
}

.about-story.in-view .about-story__title        { transition-delay: 0.12s; }
.about-story.in-view .about-story__body > *:nth-child(1) { transition-delay: 0.24s; }
.about-story.in-view .about-story__body > *:nth-child(2) { transition-delay: 0.36s; }
.about-story.in-view .about-story__body > *:nth-child(3) { transition-delay: 0.48s; }

/* --- Decorative corner lines that "draw" when the card scrolls into view --- */
.about-story__corner {
  position: absolute;
  width: 0;
  height: 0;
  border: 2px solid transparent;
  transition: width 0.7s var(--ease-out), height 0.7s var(--ease-out);
  pointer-events: none;
}

.about-story__corner--tl {
  top: -1px;
  left: -1px;
  border-top-color: var(--accent);
  border-left-color: var(--accent);
  transform-origin: top left;
}

.about-story__corner--br {
  right: -1px;
  bottom: -1px;
  border-bottom-color: var(--accent);
  border-right-color: var(--accent);
  transform-origin: bottom right;
}

/* .in-view is added by the reveal observer in script.js */
.about-story.in-view .about-story__corner {
  width: 36px;
  height: 36px;
}


/* ==========================================================================
   12. SKILLS SECTION
   --------------------------------------------------------------------------
   .skills-eyebrow → small uppercase label above the heading
   .skills-heading → large accent heading with a short rule beneath it
   .skills-cards   → 3x2 grid of numbered skill cards (2-up on phones)
   ========================================================================== */

/* --- Small label above the heading, mirrored on .journey-eyebrow --- */
.skills-eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--accent);
}

/* --- Heading with the short accent rule underneath --- */
.skills-heading {
  position: relative;
  margin-top: 12px;
  padding-bottom: 22px;
  font-family: var(--font-heading);
  font-size: clamp(1.9rem, 4.4vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--accent);
}

/* Fades out to the right so the rule looks drawn rather than boxed. */
.skills-heading::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: min(58%, 340px);
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), rgba(var(--accent-rgb), 0.15));
}

/* Group headings that split the skill cards into two labelled rows */
.skills-group-heading {
  margin: 42px 0 0;
  font-family: var(--font-heading);
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--text);
}

/* The second cards grid sits right under its group heading, so it needs
   less top margin than the first one (which follows the big section heading). */
.skills-group-heading + .skills-cards {
  margin-top: 22px;
}

/* --- 3x3 tool logo grid on a concentric-ring panel ---
   One repeating radial gradient centred on the panel paints the evenly spaced
   circles far cheaper than nested divs or an SVG data-URI. The alpha is kept
   tiny so it reads as texture rather than as a visible graphic. */
.skills-logos {
  position: relative;
  margin-top: 52px;
  padding: 46px 0;
  border-radius: 28px;
  overflow-x: clip;   /* the rings must never create a sideways scrollbar */
  background-image: repeating-radial-gradient(
    circle at 50% 50%,
    rgba(var(--accent-rgb), 0)    0px,
    rgba(var(--accent-rgb), 0)    86px,
    rgba(var(--accent-rgb), 0.07) 87px,
    rgba(var(--accent-rgb), 0)    88px
  );
}

.skills-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 44px 28px;
  list-style: none;
}

.skill-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.skill-item__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 66px;
  height: 66px;
  transition: transform 0.35s var(--ease-out), filter 0.35s var(--ease-out);
}

.skill-item__icon svg {
  width: 34px;
  height: 34px;
  stroke-width: 1.6;
  overflow: visible;
}

/* Brand logos are solid shapes, not Lucide outlines, so they get their own size.
   No `stroke` rule here on purpose: most logos are filled shapes and SVG
   already defaults stroke to none, but Claude and Node.js are drawn with
   strokes, and forcing `stroke: none` would make them invisible. */
.skill-item__icon svg.brand {
  width: 44px;
  height: 44px;
}

/* Brand marks loaded as files (assets/logos/*.svg|png) instead of inline SVG.
   Sized to exactly match svg.brand above and scaled down to fit rather than
   cropped, so a wide logo like Replit's stays whole. No background tile and no
   filter here — the file already carries its own colours. */
.skill-item__icon img.brand {
  width: 44px;
  height: 44px;
  object-fit: contain;
  display: block;
}

/* A Lucide line icon drawn at 44px instead of 34px needs a proportionally
   lighter stroke, otherwise it reads far heavier than the solid brand marks
   sitting next to it. Kept as a guard for any future line-icon chip: it only
   matches an element that is itself a Lucide svg.brand inside a skill icon. */
.skill-item__icon svg.lucide.brand {
  stroke-width: 1.25;
}

/* Hover: the logo pops and glows in its own brand colour */
.skill-item:hover .skill-item__icon {
  transform: translateY(-2px) scale(1.12);
  filter: drop-shadow(0 4px 12px var(--brand, var(--glow)));
}

.skill-item__name {
  position: relative;
  display: inline-block;
  padding-bottom: 6px;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  text-align: center;
}

/* Hover: a thin accent underline draws in under the name */
.skill-item__name::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  transition: width 0.35s var(--ease-out);
}

.skill-item:hover .skill-item__name::after,
.skill-item:focus-within .skill-item__name::after {
  width: 100%;
}

/* --- 3x2 grid of numbered skill cards --- */
.skills-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
  margin-top: 46px;
  list-style: none;
}

.skill-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 26px 24px 28px;
  border-radius: 14px;
  background: #FCEBD8;
  transition: transform 0.35s var(--ease-out), box-shadow 0.35s var(--ease-out);
}

.skill-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px rgba(42, 37, 32, 0.09);
}

/* Orange numbered badge */
.skill-card__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 8px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-family: var(--font-heading);
  font-size: 0.86rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

.skill-card__title {
  margin-top: 18px;
  font-family: var(--font-heading);
  font-size: 1.12rem;
  font-weight: 700;
  letter-spacing: -0.012em;
  color: var(--text);
  line-height: 1.32;
}

.skill-card__text {
  margin-top: 10px;
  font-size: 0.88rem;
  line-height: 1.72;
  color: var(--muted);
}

/* --- Divider between the cards and the counting stats ---
   A hairline that fades out at both ends, with a small ring at its centre,
   matching the ornament used in the Journey section. */
.skills-divider {
  position: relative;
  width: min(340px, 70%);
  height: 1px;
  margin: 92px auto 72px;
  background: linear-gradient(90deg,
    rgba(var(--accent-rgb), 0) 0%,
    rgba(var(--accent-rgb), 0.32) 22%,
    rgba(var(--accent-rgb), 0.32) 78%,
    rgba(var(--accent-rgb), 0) 100%);
}

.skills-divider::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 14px;
  border: 1.5px solid rgba(var(--accent-rgb), 0.45);
  border-radius: 50%;
  background: var(--bg);
  transform: translate(-50%, -50%);
}

/* --- Three big counting numbers --- */
.skills-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 36px;
  text-align: center;
}

.skill-stat__number {
  display: block;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: clamp(3rem, 7vw, 4.5rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;   /* stops the number jittering while counting */
}

.skill-stat__label {
  display: block;
  margin-top: 14px;
  color: var(--muted);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}


/* ==========================================================================
   12b. DIGITAL MARKETING CHIPS
   Small accent bar + heading + wrapping row of pill chips.
   Mirrors the tool-chip interaction language (hover lift, staggered reveal).
   ========================================================================== */
.digital-marketing {
  margin-top: 56px;
  text-align: center;
}

.digital-marketing__bar {
  display: block;
  width: min(60px, 18%);
  height: 3px;
  margin: 0 auto 16px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
}

.digital-marketing__heading {
  font-family: var(--font-heading);
  font-size: clamp(1.1rem, 2.2vw, 1.4rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--accent);
  margin-bottom: 20px;
}

.digital-marketing__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 14px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.digital-marketing__chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border: 1px solid var(--border);
  border-radius: 99px;
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--muted);
  background: var(--bg);
  transition:
    transform 0.25s var(--ease-out),
    border-color 0.25s var(--ease-out),
    color 0.25s var(--ease-out),
    box-shadow 0.25s var(--ease-out);
}

.digital-marketing__chip i {
  width: 16px;
  height: 16px;
  stroke-width: 2.2;
  color: var(--accent);
  flex-shrink: 0;
}

.digital-marketing__chip:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(var(--accent-rgb), 0.18);
}

.digital-marketing__chip:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 99px;
}


/* ==========================================================================
   13. PROJECTS SECTION
   --------------------------------------------------------------------------
   #projects        → faint nested-squares background (pure CSS)
   .project         → alternating two-column block
   .carousel        → screenshot frame with arrows / dots / thumbnails
   .project-info    → translucent detail card with drawing corner lines
   ========================================================================== */

/* --- Nested squares pattern (teal at ~6% over the cream background) ---
   Built from 8 hairline gradients — 4 edges of a 44px square and 4 edges of
   a smaller 16px square — tiled by a transparent 120px layer.
   To change it: --sq = tile size, --sq-c / --sq-c2 = line colours.        */
#projects {
  --sq: 120px;
  --sq-c: rgba(var(--accent-rgb), 0.06);   /* outer square  */
  --sq-c2: rgba(var(--accent-rgb), 0.045); /* inner square  */

  background-color: var(--bg);
  background-image:
    /* outer square: top, right, bottom, left edges */
    linear-gradient(var(--sq-c), var(--sq-c)),
    linear-gradient(var(--sq-c), var(--sq-c)),
    linear-gradient(var(--sq-c), var(--sq-c)),
    linear-gradient(var(--sq-c), var(--sq-c)),
    /* inner square: top, right, bottom, left edges */
    linear-gradient(var(--sq-c2), var(--sq-c2)),
    linear-gradient(var(--sq-c2), var(--sq-c2)),
    linear-gradient(var(--sq-c2), var(--sq-c2)),
    linear-gradient(var(--sq-c2), var(--sq-c2)),
    /* transparent layer — sets the tile size, do not remove */
    linear-gradient(transparent, transparent);
  background-size:
    44px 1px, 1px 44px, 44px 1px, 1px 44px,
    16px 1px, 1px 16px, 16px 1px, 1px 16px,
    var(--sq) var(--sq);
  background-position:
    38px 38px, 81px 38px, 38px 81px, 38px 38px,
    52px 52px, 67px 52px, 52px 67px, 52px 52px,
    0 0;
  background-repeat: repeat;

  /* Each project card draws a decorative corner line 34px OUTSIDE its own
     edge. That is fine visually, but it pushed past the viewport and added
     a horizontal scrollbar. Clipping horizontally trims only the part that
     was off-screen; vertical overflow is untouched, so the cards still
     animate and hover freely. */
  overflow-x: clip;
}

/* --- Project blocks --- */
.projects-list {
  display: flex;
  flex-direction: column;
  gap: 96px;
  margin-top: 68px;
}

.project {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
}

/* Alternate: even rows put the card on the left, image on the right. */
.project:nth-child(even) .project-media { order: 2; }
.project:nth-child(even) .project-info  { order: 1; }

/* Projects past the first PROJECTS_PREVIEW ones are rendered but kept out of
   the layout with display:none. .is-expanded (added by script.js on click)
   puts them back. These two rules MUST stay below .project above — they tie on
   specificity, so source order decides, and `display: grid` is what .project
   sets on every breakpoint (only its column count changes on mobile). */
.project--extra {
  display: none;
}

.projects-list.is-expanded .project--extra {
  display: grid;
}

/* --- The Show More / Show Less button row --- */
.projects-toggle {
  display: flex;
  justify-content: center;
  margin-top: 68px;
}

/* [hidden] has to beat .projects-toggle's display:flex. */
.projects-toggle[hidden] {
  display: none;
}

.btn-projects-more {
  gap: 10px;
}

/* .btn adds a "→" through ::after on every button. This one already has a
   chevron that points down (and flips up when expanded), so drop the arrow. */
.btn-projects-more::after {
  content: none;
}

.btn-projects-more .projects-toggle__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.35s var(--ease-out);
}

.projects-toggle__icon svg {
  width: 18px;
  height: 18px;
}

/* Point the chevron up once the extra projects are on screen. */
.projects-list.is-expanded ~ .projects-toggle .projects-toggle__icon {
  transform: rotate(180deg);
}

/* No transition here — .reveal already fades this, and a `transition`
   shorthand on this element would cancel that fade out. */
.project-media {
  min-width: 0;
}

/* --- IMAGE SIDE: carousel frame --- */
.carousel {
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background-color: var(--surface);
  box-shadow: 0 14px 40px rgba(42, 37, 32, 0.06);
}

.carousel-stage {
  position: relative;
  /* Overridden per project by mediaMarkup() from the `aspect` field, so a set
     of 16:9 screenshots fills the box exactly instead of being cropped. */
  aspect-ratio: var(--stage-aspect, 16 / 10);
  /* Let a vertical finger-swipe still scroll the page while we claim the
     horizontal axis for swipe-between-screenshots. */
  touch-action: pan-y;
  cursor: grab;
}

/* While a drag is in progress the three slides involved (current, next,
   previous) follow the pointer with no easing, so the movement feels 1:1. */
.carousel-stage.is-grabbing {
  cursor: grabbing;
}

.carousel-slide.is-dragging {
  opacity: 1;
  transition: none;
  will-change: transform;
}

/* The slides themselves are clipped; the arrows deliberately are not. */
.carousel-slides {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 10px;
  background-color: var(--bg-alt);
}

.carousel-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.5s var(--ease-out);
}

.carousel-slide.is-active {
  opacity: 1;
}

.carousel-slide img {
  width: 100%;
  height: 100%;
  /* `cover` fills the box and crops; `contain` shows the whole screenshot and
     letterboxes whatever does not match. A project picks this with `fit` in
     the data — website screenshots are better off with `contain`. */
  object-fit: var(--stage-fit, cover);
  cursor: zoom-in;
}

/* Hover badge hinting that a screenshot can be opened full screen. It follows
   the pointer across the stage, so it never covers the arrows. */
.carousel-zoom {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  color: #FFFFFF;
  background-color: rgba(24, 19, 14, 0.55);
  backdrop-filter: blur(4px);
  pointer-events: none;              /* clicks fall through to the image */
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}

.carousel-stage:hover .carousel-zoom,
.carousel-stage.is-grabbing .carousel-zoom {
  opacity: 1;
  transform: scale(1);
}

.carousel-zoom svg {
  width: 18px;
  height: 18px;
}

/* --- LIGHTBOX: full-screen screenshot viewer --- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 400;                     /* over the navbar (100) and menu (300) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 84px 68px;
  background-color: rgba(26, 21, 16, 0.9);
  backdrop-filter: blur(8px);
  animation: lightbox-in 0.25s var(--ease-out);
}

/* [hidden] has to beat the display:flex above. */
.lightbox[hidden] {
  display: none;
}

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

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 10px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.5);
  cursor: zoom-out;                  /* clicking the image closes it */
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  color: #FFFFFF;
  background-color: rgba(255, 255, 255, 0.08);
  transition: background-color 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}

.lightbox__btn:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  transform: scale(1.06);
}

.lightbox__btn:focus-visible {
  outline: 2px solid #FFFFFF;
  outline-offset: 3px;
}

.lightbox__btn svg {
  width: 22px;
  height: 22px;
}

.lightbox__close {
  top: 20px;
  right: 24px;
}

.lightbox__prev,
.lightbox__next {
  top: 50%;
  transform: translateY(-50%);
}

.lightbox__prev:hover,
.lightbox__next:hover {
  transform: translateY(-50%) scale(1.06);
}

.lightbox__prev { left: 24px; }
.lightbox__next { right: 24px; }

.lightbox__counter {
  position: absolute;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  color: rgba(255, 255, 255, 0.88);
  font-family: var(--font-heading);
  font-size: 0.85rem;
  letter-spacing: 0.1em;
}

.lightbox__counter[hidden],
.lightbox__btn[hidden] {
  display: none;
}

/* Stop the page behind the viewer from scrolling while it is open. */
body.lightbox-open {
  overflow: hidden;
}

/* Circular prev / next buttons */
.carousel-btn {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background-color: rgba(250, 245, 234, 0.92);
  color: var(--text);
  transform: translateY(-50%);
  transition: background-color 0.3s var(--ease-out), color 0.3s var(--ease-out),
              border-color 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}

.carousel-btn svg {
  width: 18px;
  height: 18px;
  stroke-width: 2;
}

.carousel-btn--prev { left: 12px; }
.carousel-btn--next { right: 12px; }

.carousel-btn:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 6px 18px var(--glow);
}

/* "1 / 3" counter, bottom-left */
.carousel-counter {
  position: absolute;
  bottom: 12px;
  left: 12px;
  z-index: 2;
  padding: 4px 11px;
  border-radius: 99px;
  background-color: rgba(250, 245, 234, 0.88);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  font-family: var(--font-heading);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

/* Dot indicators, bottom-right */
.carousel-dots {
  position: absolute;
  bottom: 14px;
  right: 14px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 6px;
}

.carousel-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border-radius: 99px;
  /* 0.25 alpha was almost invisible on the cream background; interactive
     controls need to be clearly perceivable. */
  background-color: rgba(42, 37, 32, 0.45);
  transition: width 0.3s var(--ease-out), background-color 0.3s var(--ease-out);
}

.carousel-dot.is-active {
  width: 20px;
  background-color: var(--accent);
}

/* Thumbnail row below the frame.
   flex-wrap means adding a 5th or 6th screenshot wraps instead of
   pushing the card wider and creating a horizontal scrollbar. */
.carousel-thumbs {
  display: flex;
  flex-wrap: wrap;
  /* Centre the strip instead of pinning it left. With only two screenshots the
     thumbnails occupy about a third of the row, and left-aligning them left a
     360px hole on one side that read as a broken empty column. Each wrapped
     row is centred independently, so it still looks right at 360px. */
  justify-content: center;
  gap: 10px;
  margin-top: 12px;
}

.carousel-thumb {
  flex: 0 0 auto;
  width: 74px;
  max-width: 100%;
  aspect-ratio: 16 / 10;
  padding: 0;
  overflow: hidden;
  border: 2px solid rgba(42, 37, 32, 0.28);
  border-radius: 7px;
  background-color: var(--bg-alt);
  opacity: 0.75;
  transition: opacity 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}

.carousel-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.carousel-thumb:hover {
  opacity: 1;
}

.carousel-thumb.is-active {
  opacity: 1;
  border-color: var(--accent);
}

/* Only one screenshot → the controls would be pointless */
.carousel.is-single .carousel-btn,
.carousel.is-single .carousel-dots,
.carousel.is-single .carousel-thumbs {
  display: none;
}

/* --- Placeholder shown when a project has no screenshots yet --- */
.project-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  aspect-ratio: 16 / 10;
  padding: 24px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  /* cream → teal gradient */
  background-image: linear-gradient(
    135deg,
    var(--bg-alt) 0%,
    rgba(var(--accent-rgb), 0.12) 100%
  );
  text-align: center;
}

.project-placeholder__icon {
  width: 30px;
  height: 30px;
  color: var(--accent);
  opacity: 0.7;
  stroke-width: 1.5;
}

.project-placeholder__title {
  font-family: var(--font-heading);
  font-size: 1.15rem;
  font-weight: 700;
}

.project-placeholder__note {
  color: var(--muted);
  font-size: 0.9rem;
}

/* --- DETAILS SIDE --- */
.project-info {
  position: relative;
  padding: 36px 38px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background-color: var(--surface);
  box-shadow: 0 14px 40px rgba(42, 37, 32, 0.06);
}

/* Slides in from its own side on scroll (see .slide-in below) */
.slide-in {
  opacity: 0;
  transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
}

.slide-in--right { transform: translateX(56px); }
.slide-in--left  { transform: translateX(-56px); }

.slide-in.in-view {
  opacity: 1;
  transform: translateX(0);
}

.project-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
}

.project-title {
  font-size: clamp(1.3rem, 2.2vw, 1.6rem);
  font-weight: 800;
  letter-spacing: -0.015em;
}

/* Small outlined tag badge, top-right */
.project-tag {
  flex-shrink: 0;
  padding: 5px 14px;
  border: 1px solid var(--accent);
  border-radius: 99px;
  color: var(--accent);
  font-family: var(--font-heading);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.project-desc {
  margin-top: 16px;
  color: var(--muted);
}

.project-subtitle {
  margin-top: 26px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
}

.project-features {
  display: grid;
  gap: 9px;
  margin-top: 12px;
}

.project-feature {
  position: relative;
  padding-left: 20px;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Small square bullets */
.project-feature::before {
  content: "";
  position: absolute;
  top: 0.62em;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background-color: var(--accent);
}

/* Outlined pill chips */
.project-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 12px;
}

.project-chip {
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: 99px;
  background-color: rgba(255, 253, 248, 0.6);
  font-size: 0.82rem;
  color: var(--text);
}

/* One or more buttons: "Visit Website", "View on GitHub", ... */
.project-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.project-links .project-link {
  margin-top: 0;
}

/* --- Corner lines that draw in when the card scrolls into view --- */
.project-corner {
  position: absolute;
  width: 0;
  height: 0;
  border: 2px solid transparent;
  transition: width 0.7s var(--ease-out), height 0.7s var(--ease-out);
  pointer-events: none;
}

.project-corner--tl {
  top: -1px;
  left: -1px;
  border-top-color: var(--accent);
  border-left-color: var(--accent);
  transform-origin: top left;
}

.project-corner--br {
  right: -1px;
  bottom: -1px;
  border-bottom-color: var(--accent);
  border-right-color: var(--accent);
  transform-origin: bottom right;
}

.project-info.in-view .project-corner {
  width: 34px;
  height: 34px;
}


/* ==========================================================================
   14. VISION SECTION
   --------------------------------------------------------------------------
   #vision           → 90vh band on --bg-alt with a glow + rotating rings
   .vision-line      → one big bold line with a teal marker band behind it
   ========================================================================== */
#vision {
  position: relative;
  display: flex;
  align-items: center;
  /* clip the oversized rings */
  overflow: hidden;
  min-height: 90vh;
  background-color: var(--bg-alt);
}

#vision .container {
  position: relative;
  z-index: 2;
  text-align: center;
}

/* --- Background layer --- */
.vision-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Soft teal radial glow in the middle */
.vision-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 780px;
  height: 780px;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    circle at center,
    var(--glow) 0%,
    rgba(var(--accent-rgb), 0.12) 38%,
    transparent 68%
  );
  opacity: 0.55;
}

.vision-rings {
  width: 100%;
  height: 100%;
}

/* Each ring rotates slowly and gently breathes (scales) */
.vision-ring {
  transform-box: fill-box;
  transform-origin: center;
}

/* Thin teal outlines at ~15% opacity */
.vision-ring path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.15;
}

.vision-ring--a { animation: vision-spin-a 110s linear infinite; }
.vision-ring--b { animation: vision-spin-b 84s linear infinite; }
.vision-ring--c { animation: vision-spin-c 132s linear infinite; }

@keyframes vision-spin-a {
  0%   { transform: rotate(0deg) scale(1); }
  50%  { transform: rotate(180deg) scale(1.05); }
  100% { transform: rotate(360deg) scale(1); }
}

@keyframes vision-spin-b {
  0%   { transform: rotate(360deg) scale(1.04); }
  50%  { transform: rotate(180deg) scale(1); }
  100% { transform: rotate(0deg) scale(1.04); }
}

@keyframes vision-spin-c {
  0%   { transform: rotate(0deg) scale(1.03); }
  50%  { transform: rotate(-180deg) scale(1); }
  100% { transform: rotate(-360deg) scale(1.03); }
}

/* --- Small heading --- */
/* "My Vision" - the small label. Kept on the old .vision-heading rule so the
   existing size/letter-spacing carries over unchanged. */
.vision-heading {
  font-family: var(--font-heading);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--muted);
}

/* "My Mission" - the large accent heading, matching the Skills section. */
.vision-title {
  margin-top: 12px;
  font-family: var(--font-heading);
  font-size: clamp(1.9rem, 4.4vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--accent);
}

/* --- The five big lines --- */
.vision-lines {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
  margin-top: 48px;
}

.vision-line {
  /* --line-delay also drives the marker band, so both stay in step. */
  --line-delay: 0.1s;

  position: relative;
  z-index: 1;
  display: inline-block;
  padding: 6px 14px;
  font-family: var(--font-heading);
  font-size: clamp(1.55rem, 4.2vw, 3rem);   /* ≈48px on desktop */
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--text);
}

/* Soft teal highlight band that wipes across like a marker.
   z-index:-1 puts it behind the line's own text (which .reveal keeps in a
   stacking context), but still in front of the section background. */
.vision-line::before {
  content: "";
  position: absolute;
  inset: 2px -10px;
  z-index: -1;
  border-radius: 9px;
  background: linear-gradient(
    90deg,
    rgba(var(--accent-rgb), 0) 0%,
    var(--accent-soft) 16%,
    var(--accent-soft) 84%,
    rgba(var(--accent-rgb), 0) 100%
  );
  transform: scaleX(0);
  transform-origin: left center;
  /* Starts slightly after the text begins fading up. */
  transition: transform 0.9s var(--ease-out) calc(var(--line-delay) + 0.2s);
}

.vision-line.in-view::before {
  transform: scaleX(1);
}

/* One after another as you scroll (four lines) */
.vision-line:nth-child(1) { --line-delay: 0.10s; transition-delay: 0.10s; }
.vision-line:nth-child(2) { --line-delay: 0.28s; transition-delay: 0.28s; }
.vision-line:nth-child(3) { --line-delay: 0.46s; transition-delay: 0.46s; }
.vision-line:nth-child(4) { --line-delay: 0.64s; transition-delay: 0.64s; }

/* Teal gradient on the closing line.
   Stops are kept in the warm brown/orange family to match --accent and are
   dark enough to clear 3:1 against the --bg-alt section background. */
.vision-line--accent {
  background-image: linear-gradient(
    100deg,
    var(--accent) 0%,
    var(--accent-2-text) 52%,
    #C2661C 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Vision paragraph that replaced the four big taglines */
.vision-text {
  position: relative;
  z-index: 1;
  max-width: 68ch;
  margin: 42px auto 0;
  text-align: center;
  font-size: 1.05rem;
  line-height: 1.9;
  color: var(--text);
}


/* ==========================================================================
   15. FAQ SECTION
   --------------------------------------------------------------------------
   .faq-item     → one <details> card, same border/radius/surface recipe as
                   .contact-form, with the same hover lift as .skill-card
   .faq-item__icon → the plus, drawn with two pseudo-elements so it needs no
                   extra markup or icon file; the vertical arm rotates 90deg
                   when open and lines up with the horizontal one to make a minus
   Colours come only from the theme variables, so re-theming :root re-themes
   this section too. Nothing here depends on JavaScript.
   ========================================================================== */
.faq-list {
  max-width: 800px;
  margin: 52px auto 0;
}

/* ---------- The card ---------- */
.faq-item {
  border: 1px solid var(--border);
  border-radius: 14px;
  background-color: var(--surface);
  box-shadow: 0 10px 26px rgba(var(--accent-rgb), 0.06);
  overflow: hidden;                 /* keeps the sliding answer inside the radius */
  transition:
    transform 0.3s var(--ease-out),
    box-shadow 0.3s var(--ease-out),
    border-color 0.3s var(--ease-out);
}

.faq-item + .faq-item {
  margin-top: 12px;
}

.faq-item:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 34px rgba(var(--accent-rgb), 0.09);
  border-color: rgba(var(--accent-rgb), 0.28);
}

/* Only the open card gets the accent border, so the open state is obvious
   even without reading the icon. */
.faq-item[open] {
  border-color: rgba(var(--accent-rgb), 0.4);
}

/* Questions arrive one after another (same :nth-child stagger as .vision-line).
   Only .reveal's own transition is delayed — the open/close animation below
   is on child elements, so it is never held up. */
.faq-item:nth-child(1) { transition-delay: 0.05s; }
.faq-item:nth-child(2) { transition-delay: 0.12s; }
.faq-item:nth-child(3) { transition-delay: 0.19s; }
.faq-item:nth-child(4) { transition-delay: 0.26s; }
.faq-item:nth-child(5) { transition-delay: 0.33s; }
.faq-item:nth-child(6) { transition-delay: 0.40s; }
.faq-item:nth-child(7) { transition-delay: 0.47s; }
.faq-item:nth-child(8) { transition-delay: 0.54s; }
.faq-item:nth-child(9) { transition-delay: 0.61s; }
.faq-item:nth-child(10) { transition-delay: 0.68s; }

/* ---------- The summary row (the clickable, focusable part) ---------- */
.faq-item__summary {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 22px;
  cursor: pointer;
  list-style: none;                 /* Firefox marker */
  transition: background-color 0.3s var(--ease-out);
}

.faq-item__summary::-webkit-details-marker {
  display: none;                    /* WebKit/Blink marker */
}

.faq-item__summary:hover {
  background-color: rgba(var(--accent-rgb), 0.05);
}

/* The summary is what the keyboard focuses, so the ring has to be visible.
   :focus-visible is already defined globally, this only trims the offset so
   the ring hugs the row instead of floating off the card. */
.faq-item__summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
  border-radius: 12px;
}

.faq-item__question {
  flex: 1;
  font-family: var(--font-heading);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  line-height: 1.45;
  text-align: left;
}

/* ---------- Plus that turns into a minus ---------- */
.faq-item__icon {
  position: relative;
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: var(--accent-soft);
  transition: background-color 0.3s var(--ease-out),
              transform 0.3s var(--ease-out);
}

.faq-item__icon::before,
.faq-item__icon::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 2px;
  background-color: var(--accent);
  transform: translate(-50%, -50%);
}

.faq-item__icon::before {
  width: 13px;
  height: 2px;
}

.faq-item__icon::after {
  width: 2px;
  height: 13px;
  transition: transform 0.3s var(--ease-out);
}

.faq-item[open] .faq-item__icon {
  transform: rotate(180deg);
}

.faq-item[open] .faq-item__icon::after {
  /* Horizontal arm folds onto the other one, leaving a single minus bar. */
  transform: translate(-50%, -50%) rotate(90deg);
}

/* ---------- Answer: collapses with no JavaScript ----------
   grid-template-rows 0fr is what actually hides the answer, so the closed
   state is guaranteed by layout alone and needs no script.

   The height is deliberately NOT transitioned, and `transition: none` is
   stated explicitly rather than left out. Without it transition-property
   falls back to its initial value `all`, and the reduced-motion rule in
   section 19 forces `transition-duration: 0.01ms !important` on every
   element — which re-creates a grid-template-rows transition that can get
   wedged in its running state and leave a closed item showing its answer.
   Naming no properties keeps the collapse correct in both motion modes.

   The soft fade below rides on top and cannot affect layout, so worst case
   the answer simply appears without it. */
.faq-item__wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: none;
}

.faq-item[open] .faq-item__wrap {
  grid-template-rows: 1fr;
}

.faq-item__body {
  min-height: 0;
  overflow: hidden;
}

.faq-item[open] .faq-item__body {
  animation: faqAnswerIn 0.34s var(--ease-out);
}

@keyframes faqAnswerIn {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.faq-item__body p {
  padding: 0 22px 20px;
  font-size: 0.93rem;
  line-height: 1.78;
  color: var(--muted);
  text-align: left;
}


/* ==========================================================================
   16. CONTACT SECTION
   ========================================================================== */

/* ---------- Form card ---------- */
.contact-form {
  position: relative;
  max-width: 700px;
  margin: 56px auto 0;
  padding: 40px 42px 34px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background-color: var(--surface);
  box-shadow: 0 14px 40px rgba(42, 37, 32, 0.06);
}

.form-field + .form-field {
  margin-top: 22px;
}

.form-label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-heading);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text);
}

.form-input {
  display: block;
  width: 100%;
  padding: 12px 15px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background-color: rgba(255, 255, 255, 0.72);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.95rem;
  transition: border-color 0.25s var(--ease-out), box-shadow 0.25s var(--ease-out);
}

.form-textarea {
  resize: vertical;
  min-height: 128px;
  line-height: 1.6;
}

.form-input::placeholder {
  color: var(--muted);
  opacity: 0.72;
}

/* Focus → teal border + soft glow */
.form-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

/* A field that failed validation */
.form-input.has-error {
  border-color: var(--accent-2-text);
}

.form-input.has-error:focus {
  box-shadow: 0 0 0 4px rgba(180, 82, 26, 0.18);
}

.form-error {
  min-height: 0;
  margin-top: 7px;
  color: var(--accent-2-text);
  font-size: 0.84rem;
  font-weight: 500;
}

.form-error:empty {
  margin-top: 0;
}

/* Full-width send button. The ✈ rides in the .btn arrow slot so it picks up
   the existing hover slide for free. */
.form-submit {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 28px;
  padding: 14px 26px;
  font-size: 0.95rem;
}

.btn-send::after {
  content: "\2708";
}

.form-status {
  min-height: 1.2em;
  margin-top: 14px;
  color: var(--accent);
  font-size: 0.92rem;
  font-weight: 600;
  text-align: center;
}

.form-status:empty {
      margin-top: 0;
      }

      /* Success / failure tint, so the message reads at a glance. */
      .form-status.is-ok {
      color: #3f7d3f;
      }

      .form-status.is-error {
      color: #b3402f;
      }

      /* --- Honeypot field: off-screen rather than display:none ---
         Bots skip display:none inputs, so this one has to stay in the layout
         and be invisible to people. */
      .hp-field {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0 0 0 0);
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
      }

      /* Button is disabled while the request is in flight, so it should not
         look clickable. */
      .form-submit[disabled] {
      opacity: 0.6;
      cursor: progress;
      pointer-events: none;
      }

/* Short teal corner lines on the top two corners */
.contact-corner {
  position: absolute;
  top: -1px;
  width: 0;
  height: 0;
  border: 2px solid transparent;
  transition: width 0.7s var(--ease-out), height 0.7s var(--ease-out);
  pointer-events: none;
}

.contact-corner--tl {
  left: -1px;
  border-top-color: var(--accent);
  border-left-color: var(--accent);
  transform-origin: top left;
}

.contact-corner--tr {
  right: -1px;
  border-top-color: var(--accent);
  border-right-color: var(--accent);
  transform-origin: top right;
}

.contact-form.in-view .contact-corner {
  width: 30px;
  height: 30px;
}

/* ---------- Social links: icon in a circle, platform name below ---------- */
.contact-social {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px 18px;
  margin-top: 44px;
}

.social-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 64px;
  color: var(--text);
  text-align: center;
  text-decoration: none;
  transition: transform 0.3s var(--ease-out);
}

.social-link__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background-color: var(--surface);
  color: var(--muted);
  transition:
    color 0.3s var(--ease-out),
    border-color 0.3s var(--ease-out),
    box-shadow 0.3s var(--ease-out);
}

/* The official logo SVG is used as a mask so the icon can take the
   platform's brand colour on hover without loading a second image. */
.social-link__glyph {
  display: block;
  width: 21px;
  height: 21px;
  background-color: currentColor;
  -webkit-mask-image: var(--social-icon);
  mask-image: var(--social-icon);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.social-link__label {
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--muted);
  transition: color 0.3s var(--ease-out);
}

.social-link__icon i,
.social-link__icon i svg {
  width: 21px;
  height: 21px;
}

.social-link__icon i svg {
  stroke-width: 1.8;
}

.social-link--github {
  --social-icon: url("/assets/logos/github.svg");
  --social-brand: #181717;
}

.social-link--linkedin {
  --social-icon: url("/assets/logos/linkedin.svg");
  --social-brand: #0a66c2;
}

.social-link--medium {
  --social-icon: url("/assets/logos/medium.svg");
  --social-brand: #000000;
}

.social-link--codepen {
  --social-icon: url("/assets/logos/codepen.svg");
  --social-brand: #000000;
}

.social-link--devto {
  --social-icon: url("/assets/logos/devdotto.svg");
  --social-brand: #0a0a0a;
}

.social-link--behance {
  --social-icon: url("/assets/logos/behance.svg");
  --social-brand: #1769ff;
}

.social-link--dribbble {
  --social-icon: url("/assets/logos/dribbble.svg");
  --social-brand: #ea4c89;
}

.social-link--email {
  --social-brand: var(--accent);
}

/* Hover: lift the whole link and tint the icon with its brand colour */
.social-link:hover {
  transform: translateY(-3px);
}

.social-link:hover .social-link__icon {
  color: var(--social-brand, var(--accent));
  border-color: var(--social-brand, var(--accent));
  box-shadow: 0 10px 26px var(--glow);
}

.social-link:hover .social-link__label {
  color: var(--text);
}

.social-link:focus-visible {
  border-radius: 12px;
}

.social-link:focus-visible .social-link__icon {
  border-color: var(--accent);
}


/* ==========================================================================
   17. BACK TO TOP
   ========================================================================== */

/* Floating "back to top" button — shown by JS after 400px of scrolling */
.to-top {
  position: fixed;
  right: 26px;
  bottom: 26px;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background-color: rgba(250, 245, 234, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--accent);
  box-shadow: 0 10px 26px rgba(42, 37, 32, 0.10);
  opacity: 0;
  visibility: hidden;
  transform: translateY(14px);
  transition: opacity 0.35s var(--ease-out), transform 0.35s var(--ease-out),
              visibility 0.35s, background-color 0.3s var(--ease-out),
              border-color 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}

.to-top svg {
  width: 20px;
  height: 20px;
  stroke-width: 2.2;
}

.to-top.visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.to-top:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 10px 26px var(--glow);
}


/* ==========================================================================
   17b. FLOATING RESUME BUTTON + MODAL
   A pill above the back-to-top button on every page. Without JavaScript the
   link opens the PDF directly; script.js upgrades it to open an in-page
   preview modal instead (with a download link inside).
   ========================================================================== */
.resume-fab {
  position: fixed;
  right: 26px;
  bottom: 84px;                     /* clears the 46px to-top button + gap */
  z-index: 80;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background-color: var(--accent);
  color: #FFFFFF;
  font-family: var(--font-heading);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  box-shadow: 0 10px 26px var(--glow);
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out),
              background-color 0.3s var(--ease-out);
}

.resume-fab svg {
  width: 16px;
  height: 16px;
}

.resume-fab:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 32px var(--glow);
}

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

/* Full-screen preview dialog (opened by script.js). */
.resume-modal {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background-color: rgba(26, 21, 16, 0.9);
  backdrop-filter: blur(8px);
  animation: lightbox-in 0.25s var(--ease-out);
}

/* The class sets display:flex, which beats the browser's default
   [hidden] { display:none } rule — without this line the dialog would stay
   visible even after script.js sets hidden=true, so Close appeared broken. */
.resume-modal[hidden] {
  display: none;
}

.resume-modal__panel {
  display: flex;
  flex-direction: column;
  width: min(860px, 100%);
  height: min(90vh, 100%);
  border: 1px solid var(--border);
  border-radius: 16px;
  background-color: var(--surface);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}

.resume-modal__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.resume-modal__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
}

.resume-modal__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.resume-modal__download {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-radius: 8px;
  background-color: var(--accent);
  color: #FFFFFF;
  font-family: var(--font-heading);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
}

.resume-modal__close {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background-color 0.25s var(--ease-out), color 0.25s var(--ease-out);
}

.resume-modal__close:hover {
  background-color: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}

.resume-modal__frame {
  flex: 1;
  width: 100%;
  border: 0;
  background-color: var(--bg-alt);
}

@media (max-width: 600px) {
  .resume-fab {
    right: 16px;
    bottom: 78px;
    padding: 9px 14px;
    font-size: 0.75rem;
  }

  .resume-modal {
    padding: 12px;
  }
}


/* ==========================================================================
   18. RESPONSIVE TWEAKS
   ========================================================================== */
@media (max-width: 900px) {
  .nav-links {
    display: none;
  }

  .hamburger {
    display: flex;
  }

  .section {
    padding: 72px 0;
  }

  .container {
    padding-inline: 20px;
  }

  /* About section stacks into one column, photo first */
  .about-intro {
    grid-template-columns: 1fr;
    gap: 44px;
  }

  .about-intro__text {
    order: 2;
  }

  .about-intro__photo {
    order: 1;
  }

  /* Sideways sliding looks odd in a single narrow column, so the photo and
     the text both rise from below instead. */
  .about-intro[data-scroll-dir] .about-intro__photo,
  .about-intro[data-scroll-dir] .about-intro__text {
    transform: translateY(40px);
  }

  .about-photo {
    width: 260px;
  }

  .about-story {
    margin-top: 60px;
    padding: 34px 26px;
  }

  /* Skill cards keep 3-up while there's room, then drop to 2 */
  .skills-cards {
    gap: 18px;
    margin-top: 40px;
  }

  /* Logo panel tightens up on tablets but stays 3-up */
  .skills-logos {
    margin-top: 40px;
    padding: 34px 0;
  }

  .skills-grid {
    gap: 34px 16px;
  }

  /* Stats stack on narrow screens */
  .skills-stats {
    grid-template-columns: 1fr;
    gap: 44px;
  }

  .skills-divider {
    margin: 60px auto 48px;
  }

  .digital-marketing__chips {
    gap: 8px 12px;
  }

  .digital-marketing__chip {
    padding: 7px 14px;
    font-size: 0.8rem;
  }

  /* Projects stack into one column — always image first */
  .project {
    grid-template-columns: 1fr;
    gap: 30px;
  }

  .project:nth-child(even) .project-media,
  .project:nth-child(even) .project-info {
    order: 0;
  }

  /* Cards slide up instead of sideways when there is no room */
  .slide-in--right,
  .slide-in--left {
    transform: translateY(30px);
  }

  .projects-list {
    gap: 68px;
    margin-top: 52px;
  }

  .projects-toggle {
    margin-top: 52px;
  }

  .project-info {
    padding: 28px 26px;
  }

  /* Vision section */
  .vision-glow {
    width: 460px;
    height: 460px;
  }

  .vision-lines {
    gap: 18px;
    margin-top: 34px;
  }

  .vision-line {
    padding: 4px 8px;
    letter-spacing: -0.01em;
  }

  .vision-line::before {
    inset: 2px -6px;
  }

  /* Contact */
  .contact-form {
    margin-top: 44px;
    padding: 30px 24px 26px;
  }

  .contact-social {
    gap: 14px;
    margin-top: 36px;
  }

  .social-link__icon {
    width: 46px;
    height: 46px;
  }

  .to-top {
    right: 18px;
    bottom: 18px;
  }
}

@media (max-width: 600px) {
  .logo {
    font-size: 1rem;
  }

  .btn {
    padding: 11px 20px;
  }

  .about-photo {
    width: 210px;
  }

  /* 3 columns is too cramped for this much body copy on a phone */
  .skills-cards {
    grid-template-columns: repeat(2, 1fr);
    gap: 16px;
  }

  /* 3 logo columns get very tight at phone width */
  .skills-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 26px 12px;
  }

  .skills-logos {
    padding: 28px 0;
  }

  .skill-item__name {
    font-size: 0.92rem;
  }

  .skill-card {
    padding: 22px 20px 24px;
  }

  .skill-card__title {
    font-size: 1.04rem;
  }
}


/* ==========================================================================
   19. REDUCED MOTION
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

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

  /* Cards would otherwise stay stuck off-screen */
  .slide-in {
    opacity: 1;
    transform: none;
  }
}
/* ==========================================================================
   JOURNEY  —  alternating centre timeline
   --------------------------------------------------------------------------
   .journey-line  → the vertical rule down the middle, fills as you scroll
   .journey-item  → one row; odd numbers sit left, even numbers sit right
   .journey-card  → the cream card with its badge, title and description

   To add a milestone: copy one <li class="journey-item"> block in the HTML.
   The alternating side is handled by :nth-child, so nothing else to change.
   ========================================================================== */

/* --- Diagonal stripe background ---
   The stripes are drawn with one repeating gradient. To lean them the other
   way, change 45deg to 135deg. */
#journey {
  background-color: var(--bg);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(168, 81, 14, 0.055) 0,
    rgba(168, 81, 14, 0.055) 1px,
    transparent 1px,
    transparent 26px
  );
}

/* ---------- Section heading ---------- */
.journey-eyebrow {
  display: block;
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
}

.journey-heading {
  margin-top: 10px;
  text-align: center;
  font-family: var(--font-heading);
  font-size: clamp(2rem, 4.6vw, 3.1rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--accent);
}

.journey-sub {
  max-width: 560px;
  margin: 14px auto 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.97rem;
  line-height: 1.7;
}

/* ---------- Timeline shell ---------- */
.journey-box {
  --dot: 15px;        /* size of the round marker on the line */
  --half: 30px;       /* half the gap between a card and the centre line */

  position: relative;
  max-width: 1000px;
  margin: 68px auto 0;
  padding: 10px 0;
  /* Cards wait off to the side before sliding in. Without this clip that
     waiting state reaches past the viewport and adds a horizontal scrollbar
     even though you are nowhere near this section. Vertical overflow is
     untouched, so the card hover lift still shows. */
  overflow-x: clip;
}

/* The vertical rule */
.journey-line {
  position: absolute;
  left: 50%;
  top: 14px;
  bottom: 14px;
  width: 2px;
  margin-left: -1px;
  background: rgba(168, 81, 14, 0.18);
  border-radius: 2px;
  overflow: hidden;
}

.journey-line-fill {
  display: block;
  width: 100%;
  height: 0;                          /* JS grows this while scrolling */
  background: var(--accent);
  box-shadow: 0 0 10px var(--glow);
  border-radius: 2px;
}

/* ---------- Rows ---------- */
.journey-list {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 26px;
}

.journey-item {
  position: relative;
  /* Leaves room for the dot, which straddles the centre line. */
  width: calc(50% - var(--half));
  opacity: 0;
  transform: translateX(-34px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}

/* Even rows sit on the right and slide in from the right */
.journey-item:nth-child(even) {
  margin-left: auto;
  transform: translateX(34px);
}

.journey-item.in-view {
  opacity: 1;
  transform: translateX(0);
}

/* Lift a card slightly on hover */
.journey-card {
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}

.journey-item.in-view .journey-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px rgba(42, 37, 32, 0.09);
}

/* The round marker sitting on the centre line */
.journey-dot {
  position: absolute;
  top: 28px;
  width: var(--dot);
  height: var(--dot);
  border-radius: 50%;
  background: var(--bg);
  border: 3px solid var(--accent);
  box-shadow: 0 0 0 4px rgba(168, 81, 14, 0.10);
}

/* The dot straddles the centre line: its centre must land exactly on it.
   The card's edge is --half away from the centre, so the dot has to travel
   --half plus half its own width to sit centred on the line. */
.journey-item:nth-child(odd) .journey-dot {
  right: calc((var(--half) + var(--dot) / 2) * -1);
}

.journey-item:nth-child(even) .journey-dot {
  left: calc((var(--half) + var(--dot) / 2) * -1);
}

.journey-item.in-view .journey-dot {
  background: var(--accent);
}

/* Arrival ripple: a ring that scales and fades — transform + opacity only,
   so the one-shot pulse never triggers paint or layout work. */
.journey-dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border: 3px solid var(--accent);
  border-radius: 50%;
  opacity: 0;
  transform: scale(0.7);
  pointer-events: none;
}

.journey-item.in-view .journey-dot::after {
  animation: journeyRipple 0.9s ease-out 1;
}

/* ---------- The card ---------- */
.journey-card {
  position: relative;
  padding: 20px 24px 22px;
  border: 1px solid rgba(168, 81, 14, 0.16);
  border-radius: 14px;
  /* Warm cream, a touch deeper than the page background */
  background-image: linear-gradient(150deg, #FDF7EE 0%, #F7EBD9 100%);
  box-shadow: 0 10px 26px rgba(42, 37, 32, 0.06);
}

.journey-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.journey-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: 99px;
  background: var(--accent);
  color: #FFFFFF;
  font-family: var(--font-heading);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The percentage chip sits beside the year in a lighter tone */
.journey-badge--score {
  background: rgba(168, 81, 14, 0.16);
  color: var(--accent);
}

/* "Pursuing" chip */
.journey-badge--live {
  background: rgba(224, 122, 63, 0.20);
  color: var(--accent-2-text);
}

.journey-card-title {
  margin-top: 12px;
  font-family: var(--font-heading);
  font-size: 1.22rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}

.journey-card-desc {
  margin-top: 8px;
  color: var(--text-soft);
  font-size: 0.94rem;
  line-height: 1.7;
}

@keyframes journeyRipple {
  from { opacity: 0.55; transform: scale(0.7); }
  to   { opacity: 0;    transform: scale(2.1); }
}

/* ---------- Mobile: line moves to the left, cards stack beside it ---------- */
@media (max-width: 760px) {
  .journey-box {
    --half: 0px;
  }

  /* Line hugs the left edge instead of the centre */
  .journey-line {
    left: 7px;
    margin-left: 0;
    top: 10px;
    bottom: 10px;
  }

  .journey-item,
  .journey-item:nth-child(even) {
    width: 100%;
    margin-left: 0;
    transform: translateY(22px);
  }

  .journey-item.in-view {
    transform: translateY(0);
  }

  /* These selectors match the desktop ones exactly, so being later in the
     file is what makes them win — a plain `.journey-dot` would lose and the
     dot would keep its desktop offset and hang off the left edge. */
  .journey-item .journey-dot,
  .journey-item:nth-child(odd) .journey-dot,
  .journey-item:nth-child(even) .journey-dot {
    top: 26px;
    left: 0;
    right: auto;
  }

  .journey-card {
    margin-left: calc(var(--dot) + 18px);
  }

  .journey-list {
    gap: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .journey-item { opacity: 1; transform: none; transition: none; }
  .journey-item.in-view .journey-dot::after { animation: none; }
}
/* ===== HERO ===== */
.hero {
  min-height: 100vh;
  /* Small viewport height accounts for mobile browser chrome, so the hero
     is not cut off when the address bar is showing. */
  min-height: 100svh;
  display: flex;
  align-items: center;
  position: relative;
  padding-top: 120px;
  padding-bottom: 90px;
  /* The decorative rings spin. A rotating square's bounding box grows by
     up to 1.41x, which poked a 3px horizontal scrollbar onto the page.
     `clip` trims that without turning the section into a scroll container
     (which `hidden` would do, and it would also clip the photo's glow). */
  overflow-x: clip;
}
.hero-inner {
  width: 100%;
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  align-items: center;
  gap: 48px;
}

/* Nudged up so the name sits high on the screen. Uses `transform` rather
   than a margin so it does not change the grid row and shift the photo. */
.hero-text {
  transform: translateY(-38px);
}

/* ----- text ----- */
/* Title uses the Elephant display font (see --font-display in :root).
   Elephant only ships one weight, so font-weight stays 400 — a bold request
   would make the browser synthesise a fake bold and look smudged.

   The gradient lives on .hero-word, NOT on .hero-title. A parent using
   `background-clip: text` breaks when a child has a transform: the child
   gets its own compositing layer, so the clipped background is painted
   behind nothing and the text disappears completely. */
.hero-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3rem, 8vw, 5.5rem);
  line-height: 1.05;
  margin-bottom: 18px;
  /* Fallback colour, in case background-clip: text is unsupported. */
  color: var(--accent);
}
.hero-word {
  display: inline-block;
  margin-right: 0.22em;
  animation: heroWordUp 0.8s ease-out both;   /* each word slides up one by one */
  animation-delay: var(--d, 0s);

  /* Warm brown gradient, top → bottom. Because every word sits on the same
     baseline, a vertical gradient lines up across all three and reads as one
     continuous headline instead of three separate ones. */
  background-image: linear-gradient(
    180deg,
    var(--accent) 0%,
    var(--accent-2-text) 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* If background-clip: text is unavailable, keep the text readable. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-word {
    background-image: none;
    -webkit-text-fill-color: currentColor;
  }
}
/* The "Curious mind..." tagline was removed — .hero-meta (the typing line)
   now sits directly under the title. */
.hero-meta {
  color: var(--muted);
  font-size: 1rem;
  letter-spacing: 0.02em;
  margin-bottom: 30px;
}
/* Gap to the buttons is handled by .hero-meta's margin-bottom, so the
   buttons stay pinned directly under the typing line. */
.hero-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.hero-fade {
  animation: heroFade 0.9s ease both;
  animation-delay: var(--d, 0s);
}

/* ----- photo + rings ----- */
/* Hover + animation live on the WRAPPER, not on .hero-photo.
   .hero-photo already has a `transform` animation (heroFloat), and a
   `transform` on hover would simply be overridden by that animation.
   The standalone `scale` property is independent of `transform`, so both
   can run at the same time. */
.hero-photo-wrap {
  display: flex;
  justify-content: center;
  scale: 1;
  transition: scale 0.5s var(--ease-out);
}

/* Hover: the whole photo gently grows */
.hero-photo-wrap:hover {
  scale: 1.045;
}

.hero-photo {
  position: relative;
  /* 100% (not 80vw) so the photo can never exceed its grid column — at
     901–1024px the column is narrower than 380px and 80vw would overflow. */
  width: min(380px, 100%);
  aspect-ratio: 1;
  animation: heroFloat 6s ease-in-out infinite;   /* gentle up/down */
}

/* Animation 2: the decorative rings speed up and brighten on hover.
   Only the duration changes, so the spin carries on smoothly instead of
   jumping back to the start. */
.hero-photo-wrap:hover .hero-ring,
.hero-photo-wrap:hover .hero-orbit {
  animation-duration: 5s;
}

.hero-ring {
  position: absolute;
  border-radius: 50%;
  transition: filter 0.5s var(--ease-out);
}
.hero-photo-wrap:hover .hero-ring--outer { filter: drop-shadow(0 0 6px var(--glow)); }
.hero-photo-wrap:hover .hero-ring--inner { filter: drop-shadow(0 0 10px var(--glow)); }
/* thin dark ring with a gap */
.hero-ring--outer {
  inset: 0;
  background: conic-gradient(rgba(42, 37, 32, 0.55) 0 72%, transparent 72% 100%);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1px));
  animation: heroSpin 24s linear infinite;
}
/* thicker teal ring, spins the opposite way */
.hero-ring--inner {
  inset: 22px;
  background: conic-gradient(var(--accent), transparent 45%, var(--accent-2) 75%, var(--accent));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 5px));
  animation: heroSpin 14s linear infinite reverse;
}
/* tiny glowing dot orbiting the outer ring */
.hero-orbit {
  position: absolute;
  inset: 0;
  animation: heroSpin 9s linear infinite;
}
.hero-orbit-dot {
  position: absolute;
  top: -5px;
  left: 50%;
  width: 10px;
  height: 10px;
  margin-left: -5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 12px 3px var(--glow);
}
.hero-avatar {
  position: absolute;
  inset: 40px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-alt);
  display: grid;
  place-items: center;
}

/* The breathing glow used to animate box-shadow, which repaints every frame.
   Now the glow is painted once on a pseudo-element and only its opacity is
   animated — a compositor-only property — so the pulse stays smooth. */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 40px;
  border-radius: 50%;
  box-shadow: 0 0 62px var(--glow);
  opacity: 0.35;
  pointer-events: none;
  animation: heroGlowPulse 4.5s ease-in-out infinite;
}

/* On hover the glow beats faster */
.hero-photo-wrap:hover .hero-photo::after {
  animation-duration: 1.6s;
}
.hero-initial {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 5rem;
  color: var(--accent);
}
.hero-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The photo is 3:4 portrait but the frame is a circle, so a centred crop
     would sit too low and push the head near the top edge. 18% keeps the
     face comfortably in the upper-middle of the circle. */
  object-position: center 20%;
}

/* <picture> is only a wrapper for the responsive sources; keeping it out of
   the layout lets the absolutely-positioned <img> behave exactly as before. */
.hero-avatar picture,
.about-photo picture {
  display: contents;
}

/* ----- scroll-down arrow ----- */
.hero-scroll {
  position: absolute;
  bottom: 28px;
  left: 50%;
  margin-left: -22px;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 50%;
  text-decoration: none;
  animation: heroBounce 1.8s ease-in-out infinite;
  transition: color 0.3s, border-color 0.3s;
}
.hero-scroll:hover { color: var(--accent); border-color: var(--accent); }

/* ----- animations ----- */
@keyframes heroWordUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: none; } }
@keyframes heroFade   { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
@keyframes heroFloat  { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
/* Slow "breathing" glow around the hero photo. Animates opacity only;
   the shadow itself is static on .hero-photo::after. */
@keyframes heroGlowPulse {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 0.8; }
}
@keyframes heroSpin   { to { transform: rotate(360deg); } }
@keyframes heroBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(10px); } }

  /* ----- mobile ----- */
  @media (max-width: 900px) {
    .hero-inner { grid-template-columns: 1fr; text-align: center; gap: 32px; }
    .hero-photo-wrap { order: -1; }          /* photo on top */
    .hero-photo { width: min(280px, 70vw); }
    .hero-buttons { justify-content: center; }
    /* The whole hero text is centred here, so the typing line follows suit. */
    .hero-meta { justify-content: center; }
  }

@media (prefers-reduced-motion: reduce) {
  .hero-word, .hero-fade, .hero-photo, .hero-photo-wrap, .hero-avatar,
  .hero-ring, .hero-orbit, .hero-scroll { animation: none; }
}
/* ===== HERO: final sizes + typing effect =====
   Title    : 5.1rem max, Elephant display font
   Meta line: the rotating typing text — left-aligned so it starts on the
               same line as the title.                                  */
.hero-title {
  font-size: clamp(2.8rem, 6.9vw, 5.1rem);
}

/* Small "Portfolio" label sitting above the name */
.hero-label {
  display: inline-block;
  margin-bottom: 14px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--accent-2-text);
}

/* The rotating line. Playfair Display ITALIC — the serif italic look in the
   reference image. The italic style is what separates it from the upright
   Elephant name above, so the colour can stay in the warm accent family. */
.hero-meta {
  /* 1.3rem floor is deliberate: the longest word the effect types,
     "Aspiring Full Stack Developer", measured 301px at 1.45rem and the line
     only has 320px on a 360px phone. 1.3rem brings it to ~270px so it can
     never wrap mid-word on a small screen. */
  font-size: clamp(1.3rem, 3.4vw, 2.3rem);
  font-weight: 500;
  font-style: italic;
  font-family: var(--font-script);
  color: #000000;              /* black, as requested */
  letter-spacing: normal;
  min-height: 1.6em;           /* stops the layout from jumping while typing */
  margin-bottom: 26px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  /* Same left edge as the title above it. Switches to centre on mobile,
     where the whole hero text is centred (see the 900px query below). */
  justify-content: flex-start;
  gap: 0;
}

/* Short intro paragraph under the rotating line */
.hero-about {
  max-width: 54ch;
  margin: 0 0 30px;
  color: var(--muted);
  font-size: 0.98rem;
  line-height: 1.75;
}

/* Compact skills line under the hero intro */
.hero-skills {
  max-width: 54ch;
  margin: 0 0 30px;
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* The blinking block that sits after the typed word */
.typed-cursor {
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 5px;
  background: #000000;        /* matches the typed text */
  vertical-align: -0.1em;
  animation: cursorBlink 0.9s steps(1) infinite;
}
@keyframes cursorBlink { 50% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .typed-cursor { animation: none; }

  /* The About photo: stop the float and the breathing glow. */
  .about-photo { animation: none; }

  /* The directional slide: show everything in place, no movement.
     Without this the columns would stay stuck off-screen. */
  .about-intro__text,
  .about-intro__photo,
  .about-intro[data-scroll-dir] .about-intro__text,
  .about-intro[data-scroll-dir] .about-intro__photo {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .about-intro__text > * { opacity: 1; transition: none; }

  /* The "My Story" card: same deal, show it in place with no movement. */
  .about-story,
  .about-story[data-scroll-dir="up"] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .about-story__title,
  .about-story__body > * {
    opacity: 1;
    transition: none;
  }
}
/* ===== JOURNEY: new format (year + title + badge) ===== */
.journey-item h3 {
  font-size: clamp(1.3rem, 2.6vw, 1.7rem);
  color: var(--accent);          /* year in brown */
  margin-bottom: 2px;
}
.journey-what {
  font-family: 'Poppins', sans-serif;
  font-weight: 700;
  font-size: clamp(1.1rem, 2.2vw, 1.35rem);
  color: var(--text);
  margin-bottom: 4px;
}
.journey-badge {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 12px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 999px;
  vertical-align: middle;
}
/* small blinking dot inside the "Pursuing" badge */
.journey-badge--live::before {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: badgePulse 1.2s ease-in-out infinite;
}
@keyframes badgePulse { 50% { opacity: 0.25; } }

@media (prefers-reduced-motion: reduce) {
  .journey-badge--live::before { animation: none; }
}
