/* ============================================================
   MAIN STYLESHEET  ·  Personal Website V2
   Sections: Fonts · Base · Buttons · Navbar · Hero · Responsive
   ============================================================ */

/* ── Google Fonts ──────────────────────────────────────────── */
/* Moved to a <link rel="stylesheet"> in index.html's <head> — @import
   here was only discovered after this whole file was fetched and parsed,
   serializing a round-trip that delayed first paint and the intro
   animation on cold-cache visits. Keep the two in sync if this ever
   changes. */

/* ── Base ──────────────────────────────────────────────────── */
html {
  font-size: 16px;
}

body {
  font-family: var(--font-latin);
  background-color: var(--bg-primary);
  color: var(--text-primary);
  letter-spacing: 0.015em;
  transition: background-color var(--transition-slow), color var(--transition-slow);
  overflow-x: hidden;
}

[dir="rtl"] body {
  font-family: var(--font-arabic);
  letter-spacing: 0; /* Arabic script must not have letter spacing */
}

/* ── Buttons ───────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  padding: 12px 24px;
  border-radius: var(--radius-pill);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  border: 1.5px solid transparent;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.25s ease,
              background-color var(--transition-fast),
              color var(--transition-fast),
              border-color var(--transition-fast);
}

/* Motion-led hover, same for both variants: a lift + shadow, no color swap.
   The arrow icon is collapsed to 0 width/opacity at rest and slides in on
   hover — see the .btn-arrow rules below. */
.btn:hover {
  transform: translateY(-3px);
}

/* Arrow icon: add `<svg class="btn-arrow">...</svg>` as the last child of
   any .btn. Uses currentColor, so it automatically matches each variant's
   text color in both themes with no extra rules needed. */
.btn-arrow {
  flex-shrink: 0;
  width: 0;
  opacity: 0;
  transform: translateX(-6px);
  margin-inline-start: -8px; /* collapses the flex `gap` while width is 0 */
  overflow: visible;
  transition: width 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
              opacity 0.25s ease,
              transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
              margin-inline-start 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.btn:hover .btn-arrow {
  width: 14px;
  opacity: 1;
  transform: translateX(0);
  margin-inline-start: 0;
}
[dir="rtl"] .btn-arrow { transform: translateX(6px) scaleX(-1); }
[dir="rtl"] .btn:hover .btn-arrow { transform: translateX(0) scaleX(-1); }

/* Primary: dark fill, white text */
.btn-dark {
  background-color: var(--text-primary);
  color: var(--text-inverse);
  border-color: var(--text-primary);
}
.btn-dark:hover {
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22);
}
[data-theme="dark"] .btn-dark:hover {
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55);
}

/* Secondary: light gray fill + border — matches Figma #F5F5F5 */
.btn-ghost-fill {
  background-color: #F5F5F5;
  color: var(--text-secondary);
  border-color: rgba(0, 0, 0, 0.12);
}
.btn-ghost-fill:hover {
  background-color: #EBEBEB;
  color: var(--text-primary);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.10);
}
[data-theme="dark"] .btn-ghost-fill {
  background-color: var(--bg-tertiary);
  color: var(--text-secondary);
  border-color: var(--border-default);
}
[data-theme="dark"] .btn-ghost-fill:hover {
  background-color: var(--bg-secondary);
  color: var(--text-primary);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
}

/* Anchor-jump landing offset — the navbar is fixed and sits on top of the
   page, so a bare #section jump (from any nav link, the hero CTAs, or the
   footer) would otherwise land the section flush under it with its
   heading clipped. This applies to every in-page anchor site-wide, not
   just the mobile menu, since they all share the same fixed navbar. */
html {
  scroll-padding-top: calc(var(--nav-height) + 16px);
}

/* ============================================================
   NAVBAR
   ============================================================ */
.navbar {
  position: fixed;
  top: 0;
  inset-inline: 0;
  z-index: 100;
  height: var(--nav-height);
  background-color: color-mix(in srgb, var(--bg-primary) 88%, transparent);
  border-bottom: 1px solid var(--border-default);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: background-color var(--transition-slow), border-color var(--transition-slow),
              box-shadow var(--transition-slow);
}

.navbar.scrolled {
  box-shadow: 0 1px 20px rgba(0, 0, 0, 0.06);
}

.nav-inner {
  height: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: 120px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-24);
}

/* ── Logo ── */
/* ── Nav identity ──────────────────────────────────────────
   Replaces the signature logo image (removed 2026-08-05). Just the name, set
   in the site's own typeface — no mark, no icon, no image. That means no
   light/dark asset swap and no per-language file; `nav.name` carries the
   script. The accent-square marker that briefly lived here was dropped on
   request along with the section-kicker squares. */
.nav-identity {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
  color: var(--text-primary);
}

.nav-identity-name {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: opacity var(--transition-fast);
}
/* Arabic must never carry Latin-style negative tracking. */
[dir="rtl"] .nav-identity-name {
  letter-spacing: 0;
}

/* Below the 900px nav breakpoint — where the links collapse into the
   hamburger and the bar gets crowded by the language switcher and the menu
   button — the identity drops to the first name only. Same breakpoint as the
   rest of the nav so there is a single place where "the nav is compact" is
   decided. Both spans exist in the DOM because js/i18n.js writes each
   [data-i18n] node independently and one node can only hold one key. */
.nav-identity-name--short { display: none; }

@media (max-width: 900px) {
  .nav-identity-name--full  { display: none; }
  /* Sized up from the desktop 16px: one short word carries far less visual
     weight than the full name did, and it now sits next to a hamburger and a
     language switcher that would otherwise out-shout it. */
  .nav-identity-name--short {
    display: inline;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.02em;
  }
  [dir="rtl"] .nav-identity-name--short { letter-spacing: 0; }
}

.nav-identity:hover .nav-identity-name {
  opacity: 0.62;
}

/* ── Links ── */
.nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-32);
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color var(--transition-fast);
  white-space: nowrap;
}
.nav-link:hover,
.nav-link.active {
  color: var(--text-primary);
}
.nav-link .chevron {
  opacity: 0.5;
  transition: transform var(--transition-fast);
  flex-shrink: 0;
}
.nav-link:hover .chevron { opacity: 0.8; }

/* ── Projects dropdown (desktop: floating panel; mobile: inline accordion,
   see the max-width:900px block below for the accordion overrides) ── */
.nav-item.has-dropdown {
  position: relative;
  /* Invisible hover bridge: extends the hoverable box down to meet the
     panel below, so moving the cursor from the trigger to the panel
     doesn't cross a dead gap and drop the :hover state. The negative
     margin cancels the extra height so the navbar row is unaffected.
     The panel's `top: 100%` sits flush against the BOTTOM of this
     padding — not offset any further — so the bridge is the only gap;
     stacking a second gap on top of it would recreate the dead zone. */
  padding-bottom: 16px;
  margin-bottom: -16px;
}

.nav-item-row {
  display: flex;
  align-items: center;
  gap: 2px;
}

.nav-dropdown-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  margin: 0;
  background: none;
  border: none;
  border-radius: var(--radius-xs);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}
.nav-dropdown-toggle:hover { color: var(--text-primary); }
.nav-dropdown-toggle .chevron {
  opacity: 0.5;
  transition: transform var(--transition-base), opacity var(--transition-fast);
}
.has-dropdown:hover .nav-dropdown-toggle .chevron,
.has-dropdown:focus-within .nav-dropdown-toggle .chevron,
.has-dropdown.is-open .nav-dropdown-toggle .chevron {
  opacity: 0.9;
  transform: rotate(180deg);
}

.nav-dropdown {
  position: absolute;
  top: 100%;
  inset-inline-start: 0;
  min-width: 240px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
  z-index: 60;
}
.has-dropdown:hover .nav-dropdown,
.has-dropdown:focus-within .nav-dropdown,
.has-dropdown.is-open .nav-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.nav-dropdown-inner {
  background-color: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.16), 0 2px 8px rgba(0, 0, 0, 0.06);
  padding: 8px;
}

.nav-dropdown-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nav-dropdown-list a {
  display: block;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}
.nav-dropdown-list a:hover,
.nav-dropdown-list a:focus-visible {
  background-color: var(--bg-secondary);
  color: var(--text-primary);
}

/* "All projects" — the exit row to the Work section. Separated from the
   four case-study links by a hairline, and given the site's slide-in
   arrow motif (see .btn-arrow) so it reads as "go somewhere" rather than
   as a fifth case study. */
.nav-dropdown-all {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border-default);
}
.nav-dropdown-all a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-weight: 600;
}
.nav-dropdown-all a::after {
  content: '→';
  flex-shrink: 0;
  opacity: 0.45;
  transition: transform var(--transition-fast), opacity var(--transition-fast);
}
[dir="rtl"] .nav-dropdown-all a::after { content: '←'; }
.nav-dropdown-all a:hover::after,
.nav-dropdown-all a:focus-visible::after {
  opacity: 1;
  transform: translateX(3px);
  color: var(--accent-blue-light);
}
[dir="rtl"] .nav-dropdown-all a:hover::after,
[dir="rtl"] .nav-dropdown-all a:focus-visible::after {
  transform: translateX(-3px);
}

/* ── Nav End ── */
.nav-end {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  flex-shrink: 0;
}

/* ── Language Switcher ── */
.lang-switcher {
  display: flex;
  align-items: center;
  background-color: #EBEBEB;
  border-radius: var(--radius-pill);
  padding: 4px;
  gap: 2px;
  transition: background-color var(--transition-slow);
}
[data-theme="dark"] .lang-switcher {
  background-color: var(--bg-tertiary);
}
[data-theme="dark"] .lang-btn:not(.active) {
  color: var(--text-secondary);
}
[data-theme="dark"] .theme-toggle {
  color: var(--text-secondary);
  border-color: rgba(255, 255, 255, 0.16);
}

.lang-btn {
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary); /* text-tertiary failed AA against the switcher pill bg */
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  line-height: 1;
  transition: all var(--transition-fast);
  cursor: pointer;
}
.lang-btn:hover { color: var(--text-primary); }
.lang-btn.active {
  background-color: var(--text-primary);
  color: var(--text-inverse);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

/* Arabic button — bigger font */
.lang-btn[data-lang-btn="ar"] {
  font-family: var(--font-arabic);
  font-size: 16px;
}

/* ── Theme Toggle ── */
.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
  flex-shrink: 0;
}
.theme-toggle:hover {
  background-color: var(--bg-secondary);
  color: var(--text-primary);
}
.theme-toggle svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}
.icon-sun  { display: none; }
.icon-moon { display: block; }
[data-theme="dark"] .icon-sun  { display: block; }
[data-theme="dark"] .icon-moon { display: none; }

/* ── Mobile-only nav extras (hidden on desktop) ── */
.nav-mobile-cta,
.nav-mobile-status,
.nav-mobile-controls { display: none; }

/* ── Mobile Hamburger ── */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 36px;
  height: 36px;
  padding: 6px;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  height: 2px;
  background-color: currentColor;
  border-radius: 2px;
  transition: all var(--transition-fast);
}

/* ============================================================
   CUSTOM CURSOR  (pointer: fine / mouse devices only)
   mix-blend-mode: difference = auto-inverts on any bg, zero mode logic
   ============================================================ */
@media (pointer: fine) {
  *, *::before, *::after { cursor: none !important; }
  input, textarea, select { cursor: text !important; }

  #cursor-dot {
    position: fixed;
    width: 5px; height: 5px;
    background: #fff;
    border-radius: 50%;
    top: 0; left: 0;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 9998;
    mix-blend-mode: difference;
    opacity: 0;
    transition: width  0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
                height 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 0.3s ease;
  }
  #cursor-ring {
    position: fixed;
    width: 36px; height: 36px;
    border: 1.5px solid #fff;
    border-radius: 50%;
    top: 0; left: 0;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 9997;
    mix-blend-mode: difference;
    opacity: 0;
    transition: width  0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
                height 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 0.3s ease;
  }

  #cursor-dot.cur-visible  { opacity: 1; }
  #cursor-ring.cur-visible { opacity: 1; }

  /* Hover: dot grows into a filled circle, ring expands.
     Resize width/height directly instead of transform:scale() — scaling a
     5px element up stretches an already-rasterized bitmap and blurs the
     edges. Resizing lets the browser redraw it crisply at 20px. */
  #cursor-dot.cur-hover  { width: 20px; height: 20px; }
  #cursor-ring.cur-hover { width: 56px; height: 56px; }

  .hero-pending #cursor-dot,
  .hero-pending #cursor-ring { opacity: 0 !important; }
}

/* ============================================================
   HERO SECTION  —  Split layout
   Left column: all content + stats (horizontal)
   Right column: portrait photo card
   ============================================================ */

/* ── Dot grid texture — pure CSS, works in both modes ── */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0,0,0,0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .hero::before {
  background-image: radial-gradient(circle, rgba(255,255,255,0.10) 1px, transparent 1px);
}

/* Lift hero content above the dot grid */
.hero > * { position: relative; z-index: 1; }

.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 100svh;
  padding-top: var(--nav-height);
  /* Centers content on wide screens: kicks in beyond ~1240px viewport */
  padding-inline: max(0px, calc(50vw - 620px));
  background-color: var(--bg-primary);
  overflow: hidden;
  transition: background-color var(--transition-slow);
}

/* ─── Left content column ────────────────────────────────── */
.hero-content {
  flex: 0 0 46%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 20px;
  padding-block: 60px;
  padding-left: clamp(24px, 8.3vw, 120px);
  padding-right: clamp(20px, 3vw, 48px);
  min-width: 0;
}

/* ── H1: "bilingual duet" display title ──────────────────────
   Two layers inside the h1 (the markup lives in each locale's hero.title
   in js/i18n.js — see the comment on the h1 in index.html):
     .ht-main  — the live title, two lines
     .ht-ghost — the OTHER script on ONE line, faint, running horizontally
                 behind it and bleeding out of the text column
   The point is to make the Arabic/English fluency visible in the first
   thing a visitor sees. The ghost deliberately slides *under* the portrait
   card (it is painted earlier in the DOM than .hero-portrait-wrap, which
   is opaque), so it reads as continuing behind the photo rather than
   stopping at the column edge.

   Sizes are in `em` off .hero-title so the pair scales as one unit and
   only the root font-size ever needs tuning.                          */
.hero-title {
  position: relative;
  /* Creates a stacking context so the ghost's negative z-index stays behind
     the title but still in front of the hero's dot-grid ::before, instead
     of falling through to the section background. */
  isolation: isolate;
  font-size: clamp(40px, 6.2vw, 84px);
  font-weight: 700;
  line-height: 0.94;
  letter-spacing: -0.045em;
  color: var(--text-primary);
}
[dir="rtl"] .hero-title {
  letter-spacing: 0;
  line-height: 1.25;
}

.hero-title .ht-main {
  position: relative;
  display: block;
}

/* Out of flow, so the h1's height is defined entirely by .ht-main and the
   ghost can never push the hero around. Centred with top:50% + translateY
   rather than a hand-tuned em offset, so it stays optically centred by
   itself when either script's size changes. */
.hero-title .ht-ghost {
  position: absolute;
  z-index: -1;
  /* inset-inline (both edges) rather than inset-inline-start: the ghost then
     spans the h1's width and simply inherits its text-align, so one rule
     covers the left-aligned desktop column and any centred layout, with no
     per-breakpoint repositioning. */
  inset-inline: 0;
  inset-block-start: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  line-height: 1;
  font-weight: 700;
  color: var(--text-primary);
  /* Held in a custom property so the entrance keyframe and the hover state
     both resolve the *current theme's* value instead of hardcoding one and
     silently breaking the other mode. */
  --ht-ghost-o: 0.07;
  opacity: var(--ht-ghost-o);
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  transition: opacity var(--transition-slow);
}

/* LTR: Latin title, Arabic ghost. Scaled UP to 1.45em because Arabic sits
   optically smaller than Latin at the same px and would otherwise vanish
   entirely behind the heavy Latin bold. */
.hero-title .ht-ghost {
  font-family: var(--font-arabic);
  font-size: 1.45em;
  letter-spacing: 0;
}

/* RTL: Arabic title, Latin ghost. Less scaling needed in this direction. */
[dir="rtl"] .hero-title .ht-ghost {
  font-family: var(--font-latin);
  font-size: 1.15em;
  letter-spacing: -0.045em;
}

/* Dark mode: white ink at a given alpha reads heavier than black ink does
   on white, so the ghost is pulled back to hold the same optical weight.
   Must stay after the base rule above to win the cascade. */
[data-theme="dark"] .hero-title .ht-ghost {
  --ht-ghost-o: 0.06;
}

/* Hovering the headline brings the second script forward — a small reward
   that reads the layer as a deliberate choice rather than a texture. */
.hero-title:hover .ht-ghost {
  opacity: calc(var(--ht-ghost-o) * 2.1);
}

/* ── HERO ENTRANCE ────────────────────────────────────────────
   One choreographed sequence, played on load.

   The signature intro overlay this used to be sequenced against was removed
   on 2026-08-05, so the hero is now the first thing that happens rather than
   the second. `body.hero-pending` (in the markup, so it applies at parse
   time) holds everything hidden; the trigger script at the end of index.html
   swaps it for `body.hero-in` after first paint. The two classes MUST swap
   together — see the note there.

   Stagger is authored as per-element animation-delay against that single
   trigger, rather than chained JS timeouts, so the whole sequence stays
   declarative and cannot drift out of sync.

   animation-fill-mode is `backwards`, NOT `both`, on purpose: `both` holds
   the end keyframe forever, and an animation's computed value outranks
   normal declarations — which would permanently pin .scroll-cue to
   opacity:1 and silently break its existing `.is-hidden` fade-on-scroll.
   `backwards` applies the from-state during the delay and then hands the
   element back to the normal cascade. */

.hero-portrait-wrap,
.hero-title .ht-l,
.hero-divider,
.hero-bio,
.hero-actions,
.hero-stats,
.scroll-cue {
  animation-duration: 0.7s;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: backwards;
}

/* Everything below is gated on `prefers-reduced-motion: no-preference`
   rather than being written unconditionally and then undone in a
   `reduce` block. That earlier shape was subtly broken: the hide rule
   `body.hero-pending .hero-bio` is specificity (0,2,1), so a
   `reduce` override written as `body .hero-bio` (0,1,1) LOST the
   cascade and the hero stayed invisible for the whole 2.6s intro before
   popping in. Scoping the hide and the animations to no-preference means
   there is nothing to override — for reduced-motion users these rules
   simply never exist. */
@media (prefers-reduced-motion: no-preference) {

  /* Hidden until the sequence starts. Keyed on .hero-pending, which is in
     the markup at parse time, so nothing can flash before JS runs. */
  body.hero-pending .hero-portrait-wrap,
  body.hero-pending .hero-title .ht-l,
  body.hero-pending .hero-divider,
  body.hero-pending .hero-bio,
  body.hero-pending .hero-actions,
  body.hero-pending .hero-stats,
  body.hero-pending .scroll-cue {
    opacity: 0;
  }

  body.hero-in .hero-portrait-wrap { animation-name: hero-portrait-in; animation-duration: 0.9s; }
  body.hero-in .hero-divider       { animation-name: hero-rule-in; animation-delay: 0.38s; }
  body.hero-in .hero-bio           { animation-name: hero-rise; animation-delay: 0.45s; }
  body.hero-in .hero-actions       { animation-name: hero-rise; animation-delay: 0.54s; }
  body.hero-in .hero-stats         { animation-name: hero-rise; animation-delay: 0.63s; }
  body.hero-in .scroll-cue         { animation-name: hero-fade; animation-delay: 0.78s; }

  body.hero-in .hero-title .ht-l {
    animation-name: ht-line-in;
    animation-duration: 1s;
  }
  body.hero-in .hero-title .ht-l:nth-child(1) { animation-delay: 0.15s; }
  body.hero-in .hero-title .ht-l:nth-child(2) { animation-delay: 0.25s; }

  /* Ghost is display:none sitewide right now, but keep its fade wired so
     re-enabling it stays a one-line change. */
  body.hero-in .hero-title .ht-ghost {
    animation: ht-ghost-in 1s cubic-bezier(0.22, 1, 0.36, 1) 0.5s backwards;
  }
}

/* ── The display title ──
   Each line animates independently. This is WORD-level, which is the finest
   split Arabic tolerates: every line here is a single whole word, so the
   cursive shaping inside it is never broken. Splitting further, into
   characters, would collapse the Arabic into disconnected letterforms.

   The settle is opacity + translateY + a small scale and blur, anchored to
   the text's own leading edge. Deliberately no clip-path, mask or
   letter-spacing animation: tashkeel sit well above the cap line and the
   medial ج drops far below the baseline, so any hard-edged reveal shaves one
   or the other, and animated tracking would pull apart the connected script.
   Transforms and filters can do neither. */
.hero-title .ht-l {
  display: block;
  transform-origin: left center;
}
[dir="rtl"] .hero-title .ht-l {
  transform-origin: right center;
}
@keyframes ht-line-in {
  from { opacity: 0; transform: translateY(0.24em) scale(1.05); filter: blur(10px); }
  to   { opacity: 1; transform: translateY(0)     scale(1);    filter: blur(0); }
}
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes hero-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* The accent rule draws itself out from the text's leading edge. scaleX
   rather than width so it stays off the layout path. */
@keyframes hero-rule-in {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}
@keyframes hero-portrait-in {
  from { opacity: 0; transform: scale(1.04); }
  to   { opacity: 1; transform: scale(1); }
}
/* Opacity only — the ghost's transform carries its vertical centring, not
   motion, so animating transform here would knock it out of alignment. */
@keyframes ht-ghost-in {
  from { opacity: 0; }
  to   { opacity: var(--ht-ghost-o); }
}

.hero-divider {
  transform-origin: left center;
}
[dir="rtl"] .hero-divider {
  transform-origin: right center;
}

/* Desktop-only: the tuned-down display size below is specifically for the
   wide two-column layout. Scoped to ≥901px so it doesn't outrank (via
   selector specificity) the mobile .hero-title font-size set inside the
   900px media query — same class of bug as the .hero flex-direction fix
   above. */
@media (min-width: 901px) {
  [dir="rtl"] .hero-title {
    /* Arabic glyphs with harakat need more vertical room; also scale down slightly
       so the large display size doesn't overwhelm the column in RTL layout */
    font-size: clamp(34px, 5vw, 68px);
  }
}

/* Ghost translation disabled sitewide (2026-08-05, on request — was already
   hidden below 900px because a nowrap ghost bleeds off both screen edges on
   the narrow mobile layout; now also off on desktop). The markup, sizing and
   entrance-animation rules above are left in place rather than deleted, so
   this is a one-line revert (delete this rule) if the effect is wanted back
   on desktop later. */
.hero-title .ht-ghost { display: none; }

/* Blue accent rule */
.hero-divider {
  width: 40px;
  height: 2px;
  background-color: #1D4ED8;
  border-radius: 2px;
}

/* Bio
   Two tiers since 2026-08-20, not one flat block. The pitch sits in primary
   ink; the closing credential sentence is wrapped in .hero-bio-sig inside the
   same <p> and drops to a small tertiary caption. That split is why the <p>
   carries data-i18n-html and why the span markup is duplicated into all three
   locale strings in js/i18n.js (switchLang overwrites this node's innerHTML on
   every language change, so markup authored only in index.html would not
   survive the first switch). */
.hero-bio {
  font-size: 16px;
  font-weight: 400;
  color: var(--text-primary);
  line-height: 1.68;
  max-width: 320px;
}

/* The credential line. The studio name inside it is a real link into the
   Studio section, which is what earns it the accent colour: per the brand
   system the accent is reserved for meaning (links, active states, stat
   numbers) and never for emphasis, so an accent-tinted non-link here would
   be exactly the decoration that rule forbids. Underline is a border rather
   than text-decoration so its colour and offset are tunable, and so Arabic
   descenders are not struck through. */
.hero-bio-sig {
  display: block;
  margin-block-start: 13px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--text-tertiary);
}

.hero-bio-sig a {
  color: var(--accent-blue);
  font-weight: 600;
  text-decoration: none;
  border-block-end: 1px solid rgba(29, 78, 216, 0.32);
  padding-block-end: 1px;
  transition: border-color var(--transition-fast, 0.2s ease),
              color var(--transition-fast, 0.2s ease);
}
.hero-bio-sig a:hover,
.hero-bio-sig a:focus-visible { border-block-end-color: var(--accent-blue); }

/* #1D4ED8 sits too close to the dark page background, so the link and its
   underline both step up to the lighter accent there. */
[data-theme="dark"] .hero-bio-sig a {
  color: var(--accent-blue-light);
  border-block-end-color: rgba(59, 130, 246, 0.34);
}
[data-theme="dark"] .hero-bio-sig a:hover,
[data-theme="dark"] .hero-bio-sig a:focus-visible {
  border-block-end-color: var(--accent-blue-light);
}

/* CTA buttons */
.hero-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ─── Stats  (horizontal row inside left column) ─────────── */
.hero-stats {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 20px;
}

.stat-item {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 10px 0 10px 16px;
  border-left: 2.5px solid var(--accent-blue-light);
  position: relative;
}

.stat-num {
  font-size: clamp(26px, 2.3vw, 34px);
  font-weight: 800;
  color: var(--accent-blue-light);
  line-height: 1;
  letter-spacing: -0.03em;
}

.stat-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-tertiary);
  line-height: 1.35;
  white-space: pre-line;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* RTL: flip border to right side */
[dir="rtl"] .stat-item {
  border-left: none;
  border-right: 2.5px solid var(--accent-blue-light);
  padding-left: 0;
  padding-right: 16px;
}

/* Arabic needs a touch more size than Latin at small sizes to read comfortably;
   the wide Latin letter-spacing must not carry over to Arabic script. */
[dir="rtl"] .stat-label {
  font-size: 13.5px;
  letter-spacing: 0;
}

/* Mobile-only grouping wrapper (see HTML). display:contents means it has zero
   layout effect here — its children stay direct flex items of .hero-content,
   exactly as before this element existed. */
.hero-bottom-group { display: contents; }

/* ─── Right portrait column ──────────────────────────────── */
.hero-portrait-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;   /* card flush to right — outer margin = padding-right only */
  padding: 32px clamp(24px, 8.3vw, 120px) 32px clamp(40px, 5vw, 80px);
}

.hero-portrait-img {
  width: 100%;
  max-width: 460px;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  object-position: center top;
  border-radius: clamp(16px, 2vw, 28px);
  display: block;
  background-color: var(--bg-secondary);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.10),
              0 4px 16px  rgba(0, 0, 0, 0.06);
}

[data-theme="dark"] .hero-portrait-img {
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.40),
              0 0 0 1px rgba(255, 255, 255, 0.06);
}

/* RTL desktop: image left, text right
   flex-direction: row in RTL context flows items R→L,
   so first child (text) lands on RIGHT, second (portrait) on LEFT.
   Scoped to ≥901px only: these selectors ([dir="rtl"] + class) are more
   specific than the plain-class mobile rules inside the 900px media query,
   so left unscoped they'd win the cascade at mobile widths too and silently
   overwrite flex-direction/padding there — wrapping in min-width keeps them
   out of that fight entirely instead of relying on specificity to lose. */
@media (min-width: 901px) {
  [dir="rtl"] .hero {
    flex-direction: row;
  }
  [dir="rtl"] .hero-content {
    /* Mirror LTR paddings: outer gap now on RIGHT, inner gap toward portrait (left) */
    padding-left:  clamp(20px, 3vw, 48px);        /* inner — gap toward portrait on left */
    padding-right: clamp(24px, 8.3vw, 120px);     /* outer — matches left edge gap in LTR */
  }
  [dir="rtl"] .hero-portrait-wrap {
    /* Swap horizontal paddings: large outer on LEFT, small inner gap on RIGHT */
    padding: 32px clamp(40px, 5vw, 80px) 32px clamp(24px, 8.3vw, 120px);
  }
}

/* Below 900px, .hero switches to a centered single-column stack (see the
   mobile hero block further down) — symmetric in both LTR and RTL, so no
   direction-specific padding is needed. */

/* ============================================================
   RESPONSIVE
   ============================================================ */

/* ── Wide screens (≥ 1600px) ── */
@media (min-width: 1600px) {
  .nav-inner { padding-inline: 120px; }
}

/* ── Tablet (≤ 1024px) ── */
@media (max-width: 1024px) {
  .nav-inner { padding-inline: 48px; }
  .hero-content { flex: 0 0 50%; }
  .hero-stats { gap: 14px; }
}

/* ── New mobile card elements — hidden on desktop ─────────── */
/* fit-content: wrapper hugs the image so the shadow doesn't bleed
   into the transparent gap that would exist with width: 100% */
.portrait-card-wrapper { width: fit-content; }
.portrait-card-bg      { display: none; }
.portrait-card-inner {
  border-radius: clamp(16px, 2vw, 28px);
  overflow: hidden;
  position: relative;       /* anchors the glare ::after */
  will-change: transform;
  transform-origin: center center;
}

/* Glare — a radial highlight that follows cursor via CSS vars set by JS */
.portrait-card-inner::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    circle at var(--glare-x, 50%) var(--glare-y, 50%),
    rgba(255, 255, 255, 0.22) 0%,
    rgba(255, 255, 255, 0.07) 38%,
    transparent 65%
  );
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.portrait-card-inner.is-tilted::after { opacity: 1; }

/* Image scale when card tilts — parallax depth */
.hero-portrait-img { transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); }
.portrait-card-inner.is-tilted .hero-portrait-img { transform: scale(1.045); }

/* ── Below 900px: hero becomes a single full-bleed image card,
   all content overlaid on top via CSS Grid stacking ─────────
   ─────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .nav-links { display: none; }
  .nav-toggle { display: flex; }
  .nav-inner  { padding-inline: 24px; }

  /* Photo becomes a proper card in the normal flow, instead of a full-
     bleed background with text overlaid on top of it. .hero-content goes
     `display: contents` here (the same trick .hero-bottom-group already
     uses on desktop, one level up) — that flattens greeting / title /
     divider / bio / actions / stats into direct flex children of .hero,
     alongside the portrait card, so `order` can slot the card in between
     them without touching the HTML. Centered composition throughout:
     with the image now a smaller, self-contained card rather than a
     full-bleed backdrop, centering it and the text together reads as one
     deliberate layout instead of a left-aligned column with a small
     photo floating below it. */
  /* GRID, not a flex column, since 2026-08-20: the two stats now sit
     BESIDE the portrait (one per side) instead of stacking under the
     CTAs, and a 3-column grid is the only way to get that without
     changing the HTML. Columns are [stat | portrait | stat]; every other
     child spans 1 / -1 and auto-places into the rows below.

     The side columns are minmax(0, 1fr) rather than 1fr so a long
     unbreakable label can never blow the track out past the viewport —
     plain 1fr floors at min-content and would push the portrait
     off-centre. `.stat-item` is then pulled toward the photo with
     justify-self (end / start), which mirrors itself in RTL for free, so
     the pair stays visually attached to the card at every width instead
     of drifting into the middle of two wide empty columns on tablets. */
  .hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    justify-items: center;
    align-content: center;
    min-height: 100svh;
    text-align: center;
    padding: calc(var(--nav-height) + 28px) 24px calc(env(safe-area-inset-bottom) + 28px);
    row-gap: 18px;
    column-gap: clamp(6px, 2vw, 22px);
  }

  .hero-content { display: contents; }

  /* .hero > * (further up) only lifts *direct* DOM children above the
     dotted-grid ::before background — display: contents changes what
     renders as a flex item, not the actual DOM parentage, so these
     grandchildren need their own stacking to not get painted under it. */
  .hero-portrait-wrap,
  .hero-title,
  .hero-divider,
  .hero-bio,
  .hero-actions,
  .stat-item {
    position: relative;
    z-index: 1;
  }

  /* Explicit placement replaces the old `order` chain (flex order has no
     effect on an explicitly-placed grid item). Only row 1 is spelled out;
     title / divider / bio / actions carry no grid-row and auto-place into
     rows 2-5 in DOM order. `.scroll-cue` is position:absolute, so it never
     enters the grid at all. */
  .hero-portrait-wrap { grid-column: 2; grid-row: 1; }

  .stat-item:first-child { grid-column: 1; grid-row: 1; justify-self: end;   align-self: center; }
  .stat-item:last-child  { grid-column: 3; grid-row: 1; justify-self: start; align-self: center; }

  /* width:100% rather than justify-self:stretch — .hero-bio has a
     max-width, and stretch on a max-width-capped box resolves to `start`,
     which would silently left-align the bio. width:100% lets the
     max-width cap it and `justify-items: center` re-centre it. */
  .hero-title,
  .hero-bio,
  .hero-actions        { grid-column: 1 / -1; width: 100%; }
  .hero-divider        { grid-column: 1 / -1; margin-inline: auto; }
  .hero-bio            { margin-inline: auto; }
  .hero-actions        { justify-content: center; }

  .hero-title {
    font-size: clamp(32px, 8.5vw, 48px);
  }

  /* Up from 15px on a wider 380px measure. The stat row that used to sit
     under the CTAs is gone from the vertical stack, so the pitch can take
     the space it freed: bigger type on a narrower measure reads as a
     statement rather than as filler between the title and the buttons. */
  .hero-bio {
    font-size: 16.5px;
    max-width: 336px;
  }
  .hero-bio-sig {
    font-size: 12px;
    margin-block-start: 12px;
  }

  /* display: contents dissolves the flex row so each .stat-item becomes a
     direct grid item of .hero and can be placed in its own column. The
     element is a plain <div> with an aria-label and no role, which is not
     exposed to assistive tech anyway, so nothing is lost semantically.
     NOTE this also kills the entrance animation that was hooked on
     .hero-stats (a display:contents box generates no box for opacity or
     animation to apply to) — it is re-hooked onto .stat-item at the
     bottom of this block. */
  .hero-stats { display: contents; }

  /* Mobile/tablet stats: spec callouts flanking the portrait, replacing
     the rounded chips that used to sit in their own full-width row under
     the CTAs (2026-08-20). The chips cost roughly 90px of vertical space
     in the most crowded part of the page; beside the photo the stats cost
     nothing and read as measurements taken off the card.

     No container of any kind here on purpose: a bordered chip 80px wide
     next to a photo looks like a UI control, an unboxed numeral + rule +
     caption looks like an annotation. The connector is the site's own
     dotted leader (same device as .exp-label-line and .cs-eyebrow::after),
     which is what makes these read as part of the numbering system rather
     than as decoration.

     Both .stat-item AND [dir="rtl"] .stat-item are listed so this isn't
     outranked by the higher-specificity RTL border rule further up the
     file — the same cascade trap fixed for .hero / .hero-title elsewhere. */
  .stat-item,
  [dir="rtl"] .stat-item {
    flex: none;
    /* min(118px, 100%): the 118px cap stops the pair sprawling across two
       wide tracks on a tablet, and the 100% stops a long Latin label
       ("CONTRIBUTED" is the worst case) from spilling out of its own
       track and past the hero padding at 360px and below, which it did
       before this was added. */
    max-width: min(118px, 100%);
    overflow-wrap: break-word;
    align-items: center;
    justify-content: center;
    gap: 7px;
    text-align: center;
    padding: 0;
    border: none;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  /* The leader has to sit BETWEEN the two spans, and ::before normally
     paints before both — so it is reordered with flex `order`, exactly the
     technique .cs-eyebrow uses for its index + leader pair.

     Two things that will look broken if changed: the box must be the full
     5px background tile tall, because the gradient circle's centre sits at
     2.5px and a 2px-tall box clips it to an invisible sliver; and it must
     be its own box rather than a background painted into .stat-num's
     padding, because repeat-x tiles across the whole background area and
     the dots would run behind the digits. */
  .stat-item::before {
    content: '';
    order: 2;
    width: 26px;
    height: 5px;
    color: var(--accent-blue);
    background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
    background-size: 5px 5px;
    background-repeat: repeat-x;
    opacity: 0.75;
  }
  [data-theme="dark"] .stat-item::before { color: var(--accent-blue-light); }

  .stat-num {
    order: 1;
    font-size: clamp(23px, 6.2vw, 28px);
    font-weight: 700;
    color: var(--accent-blue);
    font-variant-numeric: tabular-nums;
  }
  [data-theme="dark"] .stat-num { color: var(--accent-blue-light); }

  .stat-label {
    order: 3;
    /* Fluid rather than flat 10px: uppercase + 0.06em tracking makes the
       English labels the widest thing in a ~70px column on a small phone. */
    font-size: clamp(9px, 2.6vw, 10px);
    font-weight: 600;
    letter-spacing: 0.06em;
    line-height: 1.4;
  }
  /* Arabic runs a little larger and lighter, with no tracking and no
     uppercasing (neither means anything in Arabic script) — same policy as
     every other tracked label class on the site. */
  [dir="rtl"] .stat-label {
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0;
    line-height: 1.55;
    text-transform: none;
  }

  /* Entrance, re-hooked from .hero-stats (now display: contents, so it has
     no box for opacity or animation to land on) onto the two items
     themselves. Same keyframe and easing as before; the pair simply
     arrives last and slightly staggered, so the annotations settle onto
     the photo after it rather than with it. */
  @media (prefers-reduced-motion: no-preference) {
    body.hero-pending .stat-item { opacity: 0; }

    body.hero-in .stat-item {
      animation-name: hero-rise;
      animation-duration: 0.7s;
      animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
      animation-fill-mode: backwards;
    }
    body.hero-in .stat-item:first-child { animation-delay: 0.63s; }
    body.hero-in .stat-item:last-child  { animation-delay: 0.71s; }
  }

  /* ── Photo: a real card, sized to be smaller and centered — not the
     full-bleed backdrop this replaces. object-fit isn't cropping
     anything here: width/height stay auto (natural ratio) capped by
     max-width/max-height, so the browser scales the whole image down to
     fit rather than cropping into a fixed frame — the full photo is
     always visible. */
  .hero-portrait-wrap {
    flex: none;
    width: 100%;
    display: flex;
    justify-content: center;
    padding: 0;
  }

  .portrait-card-wrapper {
    position: relative;
  }

  /* Ghost card — small offset behind the card for depth, sized to match
     the smaller card via inset rather than the old full-bleed 100%. */
  .portrait-card-bg {
    display: block;
    position: absolute;
    top: 10px;
    left: 10px;
    right: -10px;
    bottom: -10px;
    border-radius: 18px;
    background-color: var(--bg-primary);
    background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
    background-size: 24px 24px;
    z-index: 0;
    animation: ghost-float 3s ease-in-out infinite alternate;
  }
  [data-theme="dark"] .portrait-card-bg {
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
  }

  @keyframes ghost-float {
    from { transform: translateY(0px); }
    to   { transform: translateY(-6px); }
  }

  .portrait-card-inner {
    position: relative;
    z-index: 1;
    background-color: var(--bg-secondary);
    box-shadow: 0 18px 46px rgba(0, 0, 0, 0.16),
                0 4px 14px  rgba(0, 0, 0, 0.08);
  }
  [data-theme="dark"] .portrait-card-inner {
    box-shadow: 0 18px 46px rgba(0, 0, 0, 0.5),
                0 0 0 1px  rgba(255, 255, 255, 0.07);
  }

  .hero-portrait-img {
    width: auto;
    height: auto;
    /* Down from min(64vw, 280px): the stats now need a column on either
       side, and at 390px the old width left them ~70px each, which wrapped
       the Arabic labels into three cramped lines. Purely proportional (42vw)
       up to the cap, so the side columns scale with the viewport instead of
       collapsing on the narrowest phones. */
    max-width: min(42vw, 250px);
    max-height: 42vh;
    object-fit: contain;
    object-position: center;
    aspect-ratio: unset; /* natural ratio — the desktop 3/4 + cover crop
      is exactly what showing the full image undoes */
    border-radius: 0; /* rounding lives on .portrait-card-inner, which
      clips the image to match */
    box-shadow: none;
    display: block;
  }
}

/* ── Small mobile (≤ 560px) ──
   Only the language switcher stays on the persistent bar — quick access to
   switch languages without opening the menu. The CTA and theme toggle are
   dropped from the bar at these widths; they move into the mobile menu
   instead, where everything (language, theme, and Contact) is already
   duplicated in full (see .nav-mobile-cta / .nav-mobile-controls) — not
   hidden, just relocated to where there's room to show them properly. */
@media (max-width: 560px) {
  .nav-end .btn           { display: none; }
  .nav-end .theme-toggle  { display: none; }

  /* .hero-content and .hero-bottom-group are `display: contents` at this
     breakpoint (see the 900px block above) — padding/gap on either would
     have no effect, since a `contents` element has no box of its own to
     apply them to. The equivalent spacing lives on .hero itself. */
  .hero { padding-inline: 16px; gap: 14px; }
  .hero-title { font-size: clamp(30px, 9vw, 44px); }
  /* max-width is deliberately NOT re-declared here: the ≤900px rule is
     already proportional (42vw), so letting it carry through avoids the
     hard size jump this breakpoint used to introduce at exactly 561→560px.
     Only the height cap tightens, to keep the hero on one screen. */
  .hero-portrait-img { max-height: 34vh; }
}

/* ── Mobile nav: open state ──
   Scoped to the same breakpoint as the rest of the mobile nav (below).
   Without this, growing the viewport past 900px while the menu is open
   (rotating a tablet, or resizing a desktop window while previewing
   breakpoints) would leave the full-screen overlay stuck on top of the
   desktop layout, since .nav-toggle — the only way to close it — is
   hidden again above 900px. JS also force-closes the menu on that same
   breakpoint crossing, as a second safety net. */
@media (max-width: 900px) {
  /* Dim scrim behind the card — standard pop-up treatment: makes the menu
     read as a distinct, prominent surface sitting above the page, and
     doubles as a big "tap anywhere to close" target. */
  .nav-backdrop {
    position: fixed;
    inset: 0;
    z-index: 98;
    background-color: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.3s ease, visibility 0.3s;
  }
  .nav-backdrop.is-visible {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  /* Mobile nav: a proper pop-up card — floating below the navbar with
     margin on every side, rounded corners and a real shadow, sized to
     its content (not stretched edge-to-edge/top-to-bottom) so it reads
     as one substantial, intentional surface instead of a bare list. */
  /* The menu as a miniature of the site itself: the same dot-grid
     texture every section sits on, hero-scale display type for the
     section links, and the site's ghost-index motif (.work-panel-ghost-num,
     .cs-more-num) at full volume — a big faint 01/02/03 sitting BEHIND
     each link's type, not beside it. Start-aligned, auto-mirrored in RTL. */
  .nav-links.open {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: calc(var(--nav-height) + 12px);
    inset-inline: 12px;
    max-height: calc(100dvh - var(--nav-height) - 24px);
    background-color: var(--bg-primary);
    background-image: radial-gradient(circle, rgba(0, 0, 0, 0.10) 1px, transparent 1px);
    background-size: 26px 26px;
    border-radius: 22px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28), 0 2px 8px rgba(0, 0, 0, 0.08);
    padding: 30px 26px calc(env(safe-area-inset-bottom) + 24px);
    gap: 18px;
    z-index: 99;
    overflow-y: auto;
    align-items: stretch;
    counter-reset: nav-sec;
  }
  [data-theme="dark"] .nav-links.open {
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.08) 1px, transparent 1px);
  }
  .nav-links.open .nav-link {
    position: relative;
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding-block: 6px;
    /* Wide enough that the first digit of the ghost index stays fully
       visible and only the second digit tucks under the title — before,
       the title started at 30px and covered almost the whole number. */
    padding-inline-start: 46px;
    font-size: clamp(29px, 8.5vw, 37px);
    font-weight: 700;
    letter-spacing: -1px;
    line-height: 1.1;
    transition: color var(--transition-fast);
  }
  [dir="rtl"] .nav-links.open .nav-link { letter-spacing: 0; }
  /* Ghost section index behind the type. Counted on the li so the
     mobile-only status/CTA/controls rows are excluded. */
  .nav-links.open > li:not(.nav-mobile-cta):not(.nav-mobile-status):not(.nav-mobile-controls) {
    counter-increment: nav-sec;
  }
  .nav-links.open .nav-link::before {
    content: counter(nav-sec, decimal-leading-zero);
    position: absolute;
    inset-inline-start: -2px;
    top: 50%;
    transform: translateY(-52%);
    z-index: -1;
    font-size: 56px;
    font-weight: 700;
    letter-spacing: -2px;
    line-height: 1;
    color: var(--text-primary);
    opacity: 0.11;
    font-feature-settings: 'tnum';
    pointer-events: none;
    transition: color var(--transition-fast), opacity var(--transition-fast);
  }
  /* Tap feedback: the ghost number fills with accent blue */
  .nav-links.open .nav-link:active::before {
    color: var(--accent-blue-light);
    opacity: 0.22;
  }
  /* The desktop sliding underline would span the ghost-number indent at
     this scale — the ghost fill above is the menu's feedback instead. */
  .nav-links.open .nav-link::after {
    display: none;
  }
  .nav-links.open .nav-mobile-cta {
    display: block;
    margin-top: 4px;
  }
  .nav-links.open .nav-mobile-cta .btn {
    width: 100%;
    justify-content: center;
    font-size: 16px;
    padding: 15px;
  }
  .nav-links.open .nav-mobile-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding-top: 20px;
    border-top: 1px solid var(--border-default);
  }

  /* This is the primary — often only — place to reach language and theme
     on narrow phones (both drop off the persistent bar below 560px), so
     they get roomier tap targets here than their bar-sized counterparts. */
  .nav-links.open .nav-mobile-controls .lang-switcher {
    padding: 5px;
    gap: 4px;
  }
  .nav-links.open .nav-mobile-controls .lang-btn {
    padding: 8px 14px;
    font-size: 15px;
  }
  .nav-links.open .nav-mobile-controls .lang-btn[data-lang-btn="ar"] {
    font-size: 17px;
  }
  .nav-links.open .nav-mobile-controls .theme-toggle {
    width: 42px;
    height: 42px;
  }
  .nav-links.open .nav-mobile-controls .theme-toggle svg {
    width: 19px;
    height: 19px;
  }

  /* Projects dropdown → inline accordion. The hover-bridge hack from the
     desktop rules is cancelled, and the panel switches from a floating
     absolute overlay to a normal-flow block that expands/collapses with
     the same 0fr/1fr grid-rows trick used by the Experience accordion
     (.expa-body), for a consistent open animation across the site. */
  .nav-links.open .nav-item.has-dropdown {
    align-self: stretch;
    padding-bottom: 0;
    margin-bottom: 0;
  }
  /* Start-aligned like every other index row, chevron snug against the
     "Projects" text (not pushed to the card's far edge). */
  .nav-links.open .nav-item-row {
    justify-content: flex-start;
    gap: 6px;
  }
  .nav-links.open .nav-dropdown-toggle {
    width: 44px;
    height: 44px;
    margin-inline-start: -2px;
  }
  .nav-links.open .nav-dropdown-toggle .chevron {
    width: 19px;
    height: 19px;
  }
  .nav-links.open .nav-dropdown {
    position: static;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 380ms cubic-bezier(0.4, 0, 0.2, 1);
  }
  .nav-links.open .nav-item.has-dropdown.is-open .nav-dropdown {
    grid-template-rows: 1fr;
  }
  /* Case studies nest under "01 Projects" as an indented group behind a
     hairline rail — containment shown structurally (a tree branch), not
     with a second set of numbers competing with the section indices.
     The rail starts under the section number, and the logical properties
     mirror the whole tree automatically in RTL. */
  .nav-links.open .nav-dropdown-inner {
    overflow: hidden;
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
  }
  .nav-links.open .nav-dropdown-list {
    align-items: stretch;
    margin-inline-start: 12px;
    padding: 8px 0 4px;
    padding-inline-start: 20px;
    border-inline-start: 1px solid var(--border-default);
    gap: 2px;
  }
  .nav-links.open .nav-dropdown-list a {
    display: flex;
    align-items: center;
    padding: 8px 0;
    font-size: 16px;
    font-weight: 500;
    color: var(--text-secondary);
    transition: color var(--transition-fast);
  }
  .nav-links.open .nav-dropdown-list a:active,
  .nav-links.open .nav-dropdown-list a:hover {
    color: var(--text-primary);
  }

  /* "All projects": the group's exit row — start-aligned with its
     siblings, separated by a short hairline, arrow trailing right after
     the text (the desktop ::after carries over). */
  .nav-links.open .nav-dropdown-all {
    align-self: stretch;
    display: flex;
    justify-content: flex-start;
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px solid var(--border-default);
  }
  .nav-links.open .nav-dropdown-all a {
    display: inline-flex;
    gap: 10px;
    font-weight: 600;
  }

  /* Availability status: the same live green pulse the footer badge
     uses, giving the menu's action zone a human "I'm here, hire me"
     opener above the CTA. */
  .nav-links.open .nav-mobile-status {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-top: 12px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text-secondary);
  }
  .nav-status-dot {
    position: relative;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--accent-green);
    flex-shrink: 0;
  }
  .nav-status-dot::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-color: var(--accent-green);
    animation: footer-pulse 2.2s ease-out infinite;
  }
  .nav-links.open .nav-mobile-cta {
    margin-top: 0;
  }
}

/* ============================================================
   NAVBAR INTERACTIONS
   ============================================================ */

/* ── Navbar compact on scroll ── */
.navbar {
  transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
              background-color var(--transition-slow),
              border-color var(--transition-slow),
              box-shadow var(--transition-slow);
}
.navbar.scrolled {
  height: 54px;
  box-shadow: 0 1px 24px rgba(0, 0, 0, 0.08);
}

/* Nav identity hover lives with the rest of the identity rules further up
   the file (the mark rotates, the name dims) — nothing extra needed here. */

/* ── Nav links: sliding blue underline ── */
.nav-link {
  position: relative;
}
.nav-link::after {
  content: '';
  position: absolute;
  bottom: -3px;
  left: 0;
  right: 0;
  height: 1.5px;
  background-color: var(--accent-blue);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.28s cubic-bezier(0.76, 0, 0.24, 1);
}
.nav-link:hover::after,
.nav-link.active::after {
  transform: scaleX(1);
}
[dir="rtl"] .nav-link::after {
  transform-origin: right center;
}

/* ── Chevron: rotates on hover ── */
.nav-link:hover .chevron {
  transform: rotate(180deg);
  opacity: 1;
}

/* ── Lang buttons: spring bounce on hover ── */
.lang-btn {
  transition: all var(--transition-fast),
              transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.lang-btn:not(.active):hover {
  transform: scale(1.1);
  color: var(--text-primary);
}

/* ── Theme toggle: icon spins on hover ── */
.theme-toggle svg {
  transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.theme-toggle:hover svg {
  transform: rotate(30deg);
}

/* ── Hamburger: smooth X morph ── */
.nav-toggle span {
  transform-origin: center;
  transition: transform 0.35s cubic-bezier(0.76, 0, 0.24, 1),
              opacity 0.2s ease,
              width 0.3s ease;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}
.nav-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ── Mobile menu: slides down + fades in ── */
@keyframes menu-drop {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (max-width: 900px) {
  .nav-links.open {
    animation: menu-drop 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
  }

  /* ── Mobile menu items: staggered fade-in ──
     Plain positional selectors (not tied to the .nav-item class, which
     only the first <li> carries) so every row — the 4 links, the "Contact
     Me" CTA, and the lang/theme controls row — animates in, instead of
     the last two rows appearing with no transition. */
  .nav-links.open > li:nth-child(1) { animation: menu-item-in 0.3s 0.05s both; }
  .nav-links.open > li:nth-child(2) { animation: menu-item-in 0.3s 0.10s both; }
  .nav-links.open > li:nth-child(3) { animation: menu-item-in 0.3s 0.15s both; }
  .nav-links.open > li:nth-child(4) { animation: menu-item-in 0.3s 0.20s both; }
  .nav-links.open > li:nth-child(5) { animation: menu-item-in 0.3s 0.25s both; }
  .nav-links.open > li:nth-child(6) { animation: menu-item-in 0.3s 0.30s both; }

  /* Arabic: rows slide in from the right, matching RTL reading direction,
     instead of reusing the LTR slide-from-left. */
  [dir="rtl"] .nav-links.open > li { animation-name: menu-item-in-rtl; }
}
@keyframes menu-item-in {
  from { opacity: 0; transform: translateX(-12px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes menu-item-in-rtl {
  from { opacity: 0; transform: translateX(12px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Empty scroll-anchor placeholder for the not-yet-built Articles/Insights
   section (nav "Articles" link points to #insights). Left completely
   unstyled it just shows the plain body background with no dot-grid
   texture, breaking the pattern between .skills-section and .exp-section
   in every theme/language. Give it the same flat bg + texture so the
   page reads as continuous until real content replaces it. */
#insights {
  position: relative;
  background-color: var(--bg-primary);
}
#insights::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
}
[data-theme="dark"] #insights::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

/* ============================================================
   EXPERIENCE SECTION
   ============================================================ */

.exp-section {
  /* Same flat background as every other section (hero/work/skills all
     use --bg-primary) so the page reads as one continuous surface with
     no seam at the section boundary, in both light and dark mode. */
  position: relative;
  background-color: var(--bg-primary);
  padding: clamp(80px, 12vw, 160px) var(--container-pad);
}
.exp-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .exp-section::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

.exp-container {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

/* ── Section header ── */
.exp-header {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ── Section kickers: numbered index ─────────────────────────
   Replaces the solid accent-blue square (2026-08-05, on request — it read
   as decoration rather than signal, and blue still does real work elsewhere
   on the page: the hero rule, stat numbers, links, active states).

   Each section now announces its position: 01 —— SELECTED WORK. Ties the
   kickers to the numbering the site already uses in the work cards, the
   studio spectrum and the mobile nav's ghost indices.

   The number is a CSS counter, not typed into the markup, so sections can
   be added, reordered or removed and the sequence renumbers itself. */
.exp-label-row {
  display: flex;
  align-items: center;
  gap: 10px;
  counter-increment: sec;
}

.exp-label-row::before {
  content: counter(sec, decimal-leading-zero);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  color: var(--text-tertiary);
  /* Tabular figures keep every index the same width so the labels all start
     on the same optical axis. Using IBM Plex Sans rather than pulling in a
     whole mono family for four digits — Plex's tabular numerals already
     read as monospaced at this size. */
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}

/* Was the accent square, then a solid dash; now a dotted leader — the device
   a table of contents uses to run an entry across to its page number. It says
   the numbers are an index of the page rather than ornament.

   Drawn as a repeating radial-gradient rather than `border-bottom: dotted`,
   because dotted borders render as tiny squares with browser-controlled
   spacing that can't be tuned. This gives real round dots on a 5px pitch. */
.exp-label-line {
  display: block;
  width: 30px;
  height: 5px;
  border-radius: 0;
  background-color: transparent;
  background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
  background-size: 5px 5px;
  background-position: center;
  background-repeat: repeat-x;
  color: var(--text-tertiary);
  flex-shrink: 0;
}

/* The counter has to be reset somewhere both page types share. <main> wraps
   the numbered sections on the homepage and every case study alike. */
main {
  counter-reset: sec;
}

.exp-label-text {
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: 2.5px;
  color: var(--text-tertiary);
  text-transform: uppercase;
}

[dir="rtl"] .exp-label-text {
  font-size: 14px;
  letter-spacing: 0;
}

.exp-title-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
}

.exp-title {
  font-size: clamp(32px, 4vw, 48px);
  font-weight: 700;
  letter-spacing: -1.5px;
  line-height: 1.08;
  color: var(--text-primary);
}

[dir="rtl"] .exp-title { letter-spacing: 0; }

.exp-subtitle {
  font-size: 15px;
  line-height: 1.75;
  color: var(--text-secondary);
  max-width: 400px;
  flex-shrink: 0;
}

.exp-role {
  color: var(--accent-blue-light);
  white-space: nowrap;
}

.exp-country {
  color: #E39B25;
  font-weight: 700;
}

/* ── Description block ── */
.exp-desc {
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.8;
}

.exp-desc p { margin: 0; }

/* ── Tags ── */
.exp-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.exp-tag {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 12px;
  /* White chip + hairline on the gray cards/panels — same pairing as
     .skill-tag, so every chip on the page reads identically. */
  background-color: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
  white-space: nowrap;
}

[dir="rtl"] .exp-tag {
  font-size: 14px;
  letter-spacing: 0;
}

/* ── Multi-role (Data Master) ── */
.exp-multi {
  display: flex;
  gap: 10px;
}

.exp-timeline-bar {
  flex-shrink: 0;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(
    to bottom,
    var(--accent-blue-light) 0%,
    var(--border-default) 100%
  );
  align-self: stretch;
  margin-block: 2px;
}

.exp-roles {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
}

.exp-role-block {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.exp-role-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.exp-dates-small {
  font-size: 14px;
  color: var(--text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}

[dir="rtl"] .exp-dates-small { font-size: 15px; }

/* ── RTL adjustments ──
   NOTE: with direction:rtl on <html>, flex rows automatically flow right→left.
   DO NOT add flex-direction:row-reverse — that undoes RTL and produces LTR order.
   Only override properties that genuinely need a physical change (padding, text-align, position). */
[dir="rtl"] .exp-subtitle { text-align: right; }

/* ── Dark mode ── */
[data-theme="dark"] .exp-tag {
  background-color: var(--bg-tertiary);
}

/* ── Responsive: tablet ── */
@media (max-width: 900px) {
  .exp-section {
    padding-block: 64px;
  }

  .exp-title-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .exp-subtitle { max-width: 100%; }

  .exp-role-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }

  [dir="rtl"] .exp-title-row   { flex-direction: column; }
  [dir="rtl"] .exp-role-header { flex-direction: column; }
}

@media (max-width: 480px) {
  .exp-tags { gap: 6px; }
  .exp-tag  { font-size: 11px; padding: 0 10px; }
  [dir="rtl"] .exp-tag { font-size: 12.5px; }
}

/* ============================================================
   OPTION A — ACCORDION (elevated)
   ============================================================ */
/* .expa-section class removed — accordion is now the main #experience section */

.expa-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Each accordion item — a rounded card, speaking the same visual language
   as .skill-card and the case-study "more" cards (bg-secondary fill,
   border-default hairline, 16px radius, accent-blue-light border on
   hover) instead of the old flat edge-to-edge divided-table look, which
   was the only sharp-cornered surface left on the site. All the
   interactions (accent rail, hover tint, peek teaser, hint) carry over
   unchanged — overflow:hidden keeps them clipped to the rounded shape. */
.expa-item {
  position: relative;
  overflow: hidden;
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: 16px;
  transition: background-color 240ms ease,
              border-color 240ms ease,
              box-shadow 240ms ease;
}
/* The base --text-tertiary (#9A9A9A) falls under AA (4.5:1) for the small
   text used throughout each row (num, period, hint, peek, location) on
   both the bg-secondary resting fill and the blue hover tint below.
   Locally darken to #6B6B6B (~4.8:1 on both) in light mode; #8F8F8F in
   dark mode clears 5.5:1 on the dark card fill. */
.expa-item { --text-tertiary: #6B6B6B; }
[data-theme="dark"] .expa-item { --text-tertiary: #8F8F8F; }

/* Hover / open: same card-hover language as .skill-card — the hairline
   warms to accent blue and a soft shadow lifts the card — plus the
   accordion's own subtle blue-tinted fill. No translateY here though:
   the list reflows as items expand, and a position shimmy on top of
   that would make the whole stack feel jumpy. (The old left accent rail
   was dropped with the card redesign — the accent border + tint already
   mark the active card; the rail read as leftover from the flat list.) */
.expa-item:hover,
.expa-item--open {
  background-color: color-mix(in srgb, var(--accent-blue) 4%, var(--bg-secondary));
  border-color: var(--accent-blue-light);
}
.expa-item--open {
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.08);
}
[data-theme="dark"] .expa-item--open {
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.4);
}

/* Trigger row */
.expa-trigger {
  display: flex;
  align-items: center;
  gap: 20px;
  width: 100%;
  /* Logical properties so this auto-mirrors for RTL: the chevron/period
     end (right in Latin, left in Arabic) gets its own 24px of breathing
     room instead of sitting flush against the row's edge. */
  padding-block: 22px;
  padding-inline-start: 16px;
  padding-inline-end: 24px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: start;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}

/* Keyboard focus ring, drawn inward so the card's overflow:hidden (needed
   to clip the rail/tints to the rounded shape) can't swallow it. */
.expa-trigger:focus-visible {
  outline: 2px solid var(--accent-blue-light);
  outline-offset: -2px;
  border-radius: 16px;
}

.expa-item--open .expa-company,
.expa-item:hover  .expa-company { color: var(--text-primary); }

/* Item number */
.expa-num {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--text-tertiary);
  font-feature-settings: 'tnum';
  flex-shrink: 0;
  min-width: 22px;
  transition: color var(--transition-base);
}

[dir="rtl"] .expa-num { font-size: 14px; letter-spacing: 0; }

/* accent-blue-light only clears AA contrast on dark backgrounds (5.28:1);
   on white it drops to 3.68:1, below the 4.5:1 needed for text this small.
   Use the darker accent-blue in light mode, accent-blue-light in dark mode. */
.expa-item--open .expa-num,
.expa-item:hover  .expa-num { color: var(--accent-blue); }

[data-theme="dark"] .expa-item--open .expa-num,
[data-theme="dark"] .expa-item:hover  .expa-num { color: var(--accent-blue-light); }

/* Company + sublabel block */
.expa-meta {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-width: 0;
}

.expa-company {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text-secondary);
  line-height: 1.2;
  transition: color var(--transition-base);
}

[dir="rtl"] .expa-company { letter-spacing: 0; }

/* Sublabel: role · location — always visible in trigger */
.expa-sublabel {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}

.expa-role-inline {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  transition: color var(--transition-base);
}

[dir="rtl"] .expa-role-inline { font-size: 15px; }

.expa-item--open .expa-role-inline { color: var(--accent-blue); }
[data-theme="dark"] .expa-item--open .expa-role-inline { color: var(--accent-blue-light); }

.expa-role-sep { color: var(--text-tertiary); opacity: 0.45; }

.expa-loc-sep {
  font-size: 11.5px;
  color: var(--border-default);
  opacity: 0.8;
  flex-shrink: 0;
}

[dir="rtl"] .expa-loc-sep { font-size: 13px; }

.expa-loc-inline {
  font-size: 13px;
  color: var(--text-tertiary);
  /* opacity removed — stacking on top of text-tertiary pushed contrast to ~2:1, well under AA */
}

[dir="rtl"] .expa-loc-inline { font-size: 14.5px; }

.expa-volunteer-badge {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #E39B25;
  text-transform: uppercase;
}

[dir="rtl"] .expa-volunteer-badge { font-size: 13px; letter-spacing: 0; }

/* Right group: hint pill + period + chevron */
.expa-right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

/* "N highlights" teaser — slides in on hover, hides when open */
.expa-hint {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-tertiary);
  opacity: 0;
  transform: translateX(4px);
  transition: opacity 220ms ease, transform 220ms ease;
  white-space: nowrap;
  pointer-events: none;
}

[dir="rtl"] .expa-hint { font-size: 14px; }

.expa-item:not(.expa-item--open):hover .expa-hint {
  opacity: 1; /* was 0.65 — combined with text-tertiary it fell to ~2:1 contrast */
  transform: translateX(0);
}

/* Period (dates) */
.expa-period {
  font-size: 13.5px;
  color: var(--text-tertiary);
  white-space: nowrap;
  text-align: right;
  line-height: 1.5;
}

.expa-period strong { color: var(--text-secondary); font-size: 14.5px; }

[dir="rtl"] .expa-period { font-size: 15px; }
[dir="rtl"] .expa-period strong { font-size: 16px; }

/* Chevron */
.expa-chevron {
  flex-shrink: 0;
  color: var(--text-tertiary);
  transition: transform var(--transition-base), color var(--transition-base);
}

.expa-item--open .expa-chevron { transform: rotate(180deg); color: var(--accent-blue-light); }
.expa-item:hover  .expa-chevron { color: var(--text-secondary); }

/* ── PEEK: one-line content teaser on hover ─────────────────── */
.expa-peek {
  position: relative;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  padding-left: 58px; /* 16px trigger padding + 22px num + 20px gap */
  transition: max-height 300ms cubic-bezier(0.4, 0, 0.2, 1),
              opacity    250ms ease;
  font-size: 14px;
  color: var(--text-tertiary);
  line-height: 1.6;
  pointer-events: none;
}

[dir="rtl"] .expa-peek { font-size: 15.5px; }

.expa-peek p { margin: 0; padding-bottom: 14px; }

/* Gradient fade at bottom of peek window — fades to the exact hover-tint
   fill the card has while the peek is visible (peek only shows on hover). */
.expa-peek::after {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 28px;
  background: linear-gradient(to bottom, transparent,
              color-mix(in srgb, var(--accent-blue) 4%, var(--bg-secondary)));
  pointer-events: none;
}

/* Reveal peek only on hover when item is closed */
.expa-item:not(.expa-item--open):hover .expa-peek {
  max-height: 70px;
  opacity: 1;
}

/* Animated expand body — CSS grid trick for height:auto animation */
.expa-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 380ms cubic-bezier(0.4, 0, 0.2, 1);
}

.expa-item--open .expa-body { grid-template-rows: 1fr; }

.expa-body-inner { overflow: hidden; }

.expa-body-content {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 8px 24px 28px 58px;
}

/* Tags need no special-casing here — .exp-tag's base rule (white chip +
   hairline, dark bg-tertiary) is designed for gray cards like this one. */

/* ── RTL accordion overrides ─────────────────────────────────
   direction:rtl on <html> makes flex rows flow RIGHT→LEFT automatically.
   DOM order [num][meta][right-group] naturally becomes:
     RIGHT: 01 | Company + sublabel | period + chevron :LEFT   ✓
   DOM order [role][sep][location] in sublabel naturally becomes:
     RIGHT: role | · | location :LEFT   ✓
   DOM order [role][dates] in role-header naturally becomes:
     RIGHT: role | dates :LEFT   ✓
   Only override PHYSICAL properties (padding, position, border-radius). */

/* Trigger padding/text-align now use logical properties (padding-inline-*,
   text-align:start) on the base rule, so they already auto-mirror here —
   no [dir="rtl"] override needed. */

/* Period: dates are on the LEFT side in RTL, so left-align text within the box */
[dir="rtl"] .expa-period    { text-align: left; }

/* Peek indent: mirror to right side */
[dir="rtl"] .expa-peek      { padding-left: 0; padding-right: 58px; }

/* Hint slide: in RTL, hint is rightmost in right-group (near center of trigger),
   so it slides in from the right (positive X) — same physical direction as LTR */
[dir="rtl"] .expa-hint      { transform: translateX(4px); }
[dir="rtl"] .expa-item:not(.expa-item--open):hover .expa-hint { transform: translateX(0); }

/* Body content indent: mirror to right */
[dir="rtl"] .expa-body-content { padding: 8px 58px 28px 24px; }

/* Responsive */
@media (max-width: 640px) {
  .expa-trigger        { gap: 12px; flex-wrap: wrap; padding-inline-start: 12px; padding-inline-end: 16px; }
  /* Right-group wraps to its own row; row-reverse puts chevron first (left edge in LTR) */
  .expa-right          { flex: 0 0 100%; padding-left: 34px; flex-direction: row-reverse; justify-content: flex-start; }
  .expa-company        { font-size: 16px; }
  .expa-hint           { display: none; }
  .expa-body-content   { padding-left: 34px; padding-right: 16px; }
  .expa-peek           { padding-left: 34px; }

  /* RTL responsive: .expa-trigger padding already auto-mirrors (logical
     properties); .expa-right still needs its physical padding/flex swapped. */
  [dir="rtl"] .expa-right          { padding-left: 0; padding-right: 34px; flex-direction: row; justify-content: flex-end; }
  [dir="rtl"] .expa-body-content   { padding-left: 16px; padding-right: 34px; }
  [dir="rtl"] .expa-peek           { padding-left: 0; padding-right: 34px; }
}

/* ============================================================
   WORK / CASE STUDIES SECTION — interactive case-study explorer
   ============================================================ */
.work-section {
  /* Same flat --bg-primary + dot-grid texture as the hero and every other
     section — one continuous page background. Deliberately NOT
     `overflow: hidden` — .work-tablist uses position:sticky on desktop,
     which any overflow clipping on an ancestor would silently break.
     Since the ::before below is sized with plain inset:0, it never
     extends past this box, so no clipping is needed to contain it anyway. */
  position: relative;
  background-color: var(--bg-primary);
  padding: clamp(80px, 12vw, 160px) var(--container-pad);
}
.work-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .work-section::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

.work-container {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

/* ── Explorer shell ── */
.work-explorer {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 32px;
  align-items: start;
}

/* ── Tab list (left rail) ── */
.work-tablist {
  position: sticky;
  top: calc(var(--nav-height) + 24px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline-start: 3px;
}

.work-tab {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  background: transparent;
  text-align: start;
}

.work-tab-num {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-tertiary);
  flex-shrink: 0;
  padding-top: 2px;
  transition: color 0.25s ease;
  font-variant-numeric: tabular-nums;
}

.work-tab[aria-selected="true"] .work-tab-num {
  color: var(--accent-blue);
}
[data-theme="dark"] .work-tab[aria-selected="true"] .work-tab-num {
  color: var(--accent-blue-light);
}

.work-tab-info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.work-tab-company {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.3;
}
[dir="rtl"] .work-tab-company { font-size: 16px; }

.work-tab-hook {
  font-size: 13px;
  color: var(--text-tertiary);
}
[dir="rtl"] .work-tab-hook { font-size: 14.5px; }

/* Short label — mobile-only compact tab text (see responsive block below).
   Full titles wrap to 2 uneven lines at mobile tab widths; this keeps each
   pill single-line and uniform height. */
.work-tab-short { display: none; }

/* Sliding active-tab indicator — position/size set by js/work.js */
.work-tab-indicator {
  position: absolute;
  top: 0;
  left: 0;
  width: 3px;
  height: 0;
  border-radius: 2px;
  background-color: var(--accent-blue);
  transform: translateY(0);
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), height 0.3s ease;
  pointer-events: none;
}
[data-theme="dark"] .work-tab-indicator {
  background-color: var(--accent-blue-light);
}
[dir="rtl"] .work-tab-indicator { left: auto; right: 0; }

/* The tab row: this element now carries the card look (background,
   padding, rounding) that used to live on .work-tab directly, so that the
   "open case study" button added below renders INSIDE the same card as
   the tab text rather than floating past its edge. Left position:static
   on purpose — js/work.js's sliding indicator measures .work-tab's
   offsetTop against its nearest POSITIONED ancestor, which needs to stay
   .work-tablist. Selected/hover state is read straight off the button
   via :has() rather than a class js/work.js would need to maintain. */
.work-tab-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 16px 18px;
  border-radius: var(--radius-md);
  transition: background-color 0.25s ease;
}

.work-tab-item:hover {
  background-color: rgba(0, 0, 0, 0.035);
}
[data-theme="dark"] .work-tab-item:hover {
  background-color: rgba(255, 255, 255, 0.045);
}

.work-tab-item:has(.work-tab[aria-selected="true"]) {
  background-color: rgba(0, 0, 0, 0.045);
}
[data-theme="dark"] .work-tab-item:has(.work-tab[aria-selected="true"]) {
  background-color: rgba(255, 255, 255, 0.06);
}

.work-tab-item .work-tab {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}

/* Compact "open case study" pill, shown only on the selected tab's row, so
   the case study can be opened right there instead of scrolling down to
   the CTA at the bottom of the (often tall) detail panel on the right.
   display:none by default rather than an opacity/visibility fade — it
   also removes the other three (currently unselected) copies from the
   keyboard tab order for free, same guarantee the fade version needed
   visibility:hidden for. */
.work-tab-open {
  flex-shrink: 0;
  display: none;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  border-radius: 999px;
  background-color: var(--text-primary);
  color: var(--bg-primary);
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.work-tab-open svg { flex-shrink: 0; }

.work-tab-open:hover,
.work-tab-open:focus-visible {
  background-color: var(--accent-blue);
  color: #fff;
}
[data-theme="dark"] .work-tab-open:hover,
[data-theme="dark"] .work-tab-open:focus-visible {
  background-color: var(--accent-blue-light);
  color: var(--bg-primary);
}

/* aria-selected is already toggled by js/work.js on every tab switch, so
   no extra JS is needed here to show/hide the pill. */
.work-tab[aria-selected="true"] ~ .work-tab-open {
  display: inline-flex;
}

@media (max-width: 900px) {
  /* .work-tab-item stays a real, laid-out box here rather than
     display:contents — Safari has a long-standing bug where a
     display:contents element's children don't reliably pick up the
     parent flex container's `gap`, which is exactly what produced the
     overlapping tab pills reported on the tablet/phone build. inline-flex
     shrink-wraps the item to .work-tab's own content width instead,
     matching the sizing .work-tab already handled on its own before this
     wrapper existed. The open button is out of scope for mobile/tablet
     (the tab strip is already compact at this width), so the wrapper's
     desktop card look (background, padding, rounding) is reset here and
     handed back to .work-tab directly — again, exactly as it was before
     the open button existed. Selectors are repeated at matching
     specificity to the unscoped rules they override, so source order (this
     block comes later in the file) settles the cascade regardless of which
     tab is selected. */
  .work-tab-item {
    display: inline-flex;
    flex-shrink: 0;
    width: auto;
    padding: 0;
    border-radius: 0;
  }
  .work-tab-item:hover,
  .work-tab-item:has(.work-tab[aria-selected="true"]) {
    background-color: transparent;
  }

  .work-tab {
    border-radius: var(--radius-md);
    transition: background-color 0.25s ease;
  }
  .work-tab:hover {
    background-color: rgba(0, 0, 0, 0.035);
  }
  [data-theme="dark"] .work-tab:hover {
    background-color: rgba(255, 255, 255, 0.045);
  }
  .work-tab[aria-selected="true"] {
    background-color: rgba(0, 0, 0, 0.045);
  }
  [data-theme="dark"] .work-tab[aria-selected="true"] {
    background-color: rgba(255, 255, 255, 0.06);
  }

  .work-tab[aria-selected="true"] ~ .work-tab-open,
  .work-tab-open {
    display: none;
  }
}

/* ── Detail panels (right) ── */
.work-panels {
  position: relative;
  /* Same gray fill as the Experience and Skills cards — one card color
     across the page, in both modes (bg-secondary auto-resolves in dark). */
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  padding: 48px;
  overflow: hidden;
  min-height: 520px;
}

.work-panel {
  display: none;
  position: relative;
  z-index: 1;
}

.work-panel.is-active {
  display: block;
  animation: work-panel-in 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes work-panel-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.work-panel-ghost-num {
  position: absolute;
  top: -10px;
  right: -10px;
  font-size: clamp(120px, 16vw, 200px);
  font-weight: 800;
  line-height: 1;
  color: var(--text-primary);
  opacity: 0.035;
  z-index: 0;
  pointer-events: none;
  user-select: none;
}
[dir="rtl"] .work-panel-ghost-num { right: auto; left: -10px; }

.work-panel-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}

.work-panel-tag {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-blue);
}
[data-theme="dark"] .work-panel-tag { color: var(--accent-blue-light); }
[dir="rtl"] .work-panel-tag { font-size: 14px; letter-spacing: 0; }

.work-panel-nda {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-style: italic;
  color: var(--text-tertiary);
}
[dir="rtl"] .work-panel-nda { font-size: 13.5px; }

.work-panel-title {
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 700;
  letter-spacing: -0.4px;
  color: var(--text-primary);
  margin-bottom: 6px;
}
[dir="rtl"] .work-panel-title { letter-spacing: 0; }

.work-panel-meta {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 24px;
}
[dir="rtl"] .work-panel-meta { font-size: 16px; }

/* ── Problem → Approach → Outcome flow ── */
.work-panel-flow {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1fr;
  gap: 20px;
  align-items: start;
  margin-bottom: 32px;
}

.work-flow-step {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.work-flow-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
[dir="rtl"] .work-flow-label { font-size: 13.5px; letter-spacing: 0; }

.work-flow-step p {
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--text-secondary);
}
[dir="rtl"] .work-flow-step p { font-size: 16px; }

/* Darker green in light mode only — plain accent-green fails AA (2.28:1)
   on a white card at this weight/size; #15803D clears 4.5:1 (5.02:1). */
.work-flow-step--outcome .work-flow-label { color: #15803D; }
[data-theme="dark"] .work-flow-step--outcome .work-flow-label { color: var(--accent-green); }

.work-flow-arrow {
  flex-shrink: 0;
  color: var(--text-tertiary);
  margin-top: 3px;
  opacity: 0.5;
}
[dir="rtl"] .work-flow-arrow { transform: scaleX(-1); }

/* ── Stats ── */
.work-panel-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  padding: 24px 0;
  border-top: 1px solid var(--border-default);
  border-bottom: 1px solid var(--border-default);
  margin-bottom: 24px;
}

.work-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.work-stat-value {
  display: flex;
  align-items: baseline;
  gap: 2px;
}

.work-stat-num {
  font-size: 34px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
}

.work-stat-suffix {
  font-size: 20px;
  font-weight: 700;
  color: var(--accent-blue);
}
[data-theme="dark"] .work-stat-suffix { color: var(--accent-blue-light); }

.work-stat-label {
  font-size: 13px;
  color: var(--text-tertiary);
  max-width: 140px;
  line-height: 1.4;
}
[dir="rtl"] .work-stat-label { font-size: 14.5px; }

.work-panel-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.work-panel-cta {
  margin-top: 28px;
}

/* ── Responsive ── */
@media (max-width: 900px) {
  .work-section { padding-block: 64px; }

  .work-explorer {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .work-tablist {
    /* Was `static` — that breaks the indicator's positioning context (its
       containing block becomes some distant ancestor instead of the
       tablist), so it renders detached from the tabs entirely. Needs to
       stay a positioned ancestor; just drop the desktop sticky behavior.
       `top` must also be reset — `position:relative` still honors it, so
       the desktop sticky offset (~nav-height + 24px) was shifting the
       whole tab row down into the panel content below it. */
    position: relative;
    top: auto;
    flex-direction: row;
    overflow-x: auto;
    gap: 8px;
    padding-inline-start: 0;
    padding-bottom: 6px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .work-tablist::-webkit-scrollbar { display: none; }

  .work-tab {
    flex-shrink: 0;
    width: auto;
    max-width: none;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border: 1px solid var(--border-default);
  }

  .work-tab-company,
  .work-tab-hook { display: none; }

  .work-tab-short {
    display: inline-block;
    font-size: 13.5px;
    font-weight: 700;
    color: var(--text-primary);
    white-space: nowrap;
  }
  [dir="rtl"] .work-tab-short { font-size: 15px; }

  .work-tab-indicator {
    top: auto;
    bottom: 0;
    left: 0;
    right: auto;
    width: 0;
    height: 3px;
    transform: translateX(0);
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), width 0.3s ease;
  }
  /* The unconditional `[dir="rtl"] .work-tab-indicator { right:0 }` rule
     above (outside any media query) has higher specificity (0,2,0) than
     the plain mobile rule (0,1,0) right above, so it was still winning on
     RTL + mobile and pinning the indicator's anchor to the right edge —
     the opposite of what the offsetLeft-based translateX math assumes.
     Same fix pattern as the RTL mobile-hero width bug: re-declare at
     matching specificity inside this media query so it actually wins. */
  [dir="rtl"] .work-tab-indicator {
    left: 0;
    right: auto;
  }

  .work-panels {
    padding: 28px 24px;
    min-height: 0;
  }

  .work-panel-flow {
    grid-template-columns: 1fr;
    gap: 20px;
  }
  .work-flow-arrow { display: none; }

  .work-panel-ghost-num { display: none; }

  .work-stat-label { max-width: none; }
}

/* ============================================================
   SERVICES SECTION
   Rebuilt for scanning. Three cards of identical shape (promise line +
   four bullets + note + foot + link), so they are the same height by
   construction rather than by luck, and a buyer reads a list instead of
   a paragraph. Only card 01 carries a figure and a chip: it is the
   cheapest, fastest yes, so it is where the eye should land first.
   ============================================================ */
.services-section {
  position: relative;
  background-color: var(--bg-primary);
  padding: clamp(80px, 12vw, 160px) var(--container-pad);
}
.services-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .services-section::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

.services-container {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(36px, 4.5vw, 56px);
}

/* ── The three cards ── */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 1.8vw, 24px);
  align-items: stretch;
}

.svc-card {
  display: flex;
  flex-direction: column;
  padding: clamp(22px, 2.4vw, 30px);
  /* Same gray fill as .work-panels, .skill-card and the Experience
     accordion — one card color across the page (bg-secondary auto-
     resolves per theme, so dark mode is unaffected). */
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: 20px;
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 0.25s ease, border-color 0.25s ease;
}
.svc-card:hover {
  transform: translateY(-4px);
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.08);
}
[data-theme="dark"] .svc-card:hover {
  border-color: rgba(255, 255, 255, 0.16);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.4);
}
/* The entry point gets a hairline of accent along the top edge, not a
   coloured fill: enough to catch the eye first without turning the row
   into a pricing table with a "recommended" tier. */
.svc-card--entry { border-block-start: 2px solid var(--accent-blue); }

.svc-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-block-end: 14px;
}
.svc-idx {
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  color: var(--text-tertiary);
}
.svc-chip {
  padding: 4px 10px;
  border-radius: 50px;
  background-color: rgba(29, 78, 216, 0.10);
  color: var(--accent-blue);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
[data-theme="dark"] .svc-chip {
  background-color: rgba(59, 130, 246, 0.16);
  color: var(--accent-blue-light);
}
/* Arabic script never carries Latin-style tracking or forced casing. */
[dir="rtl"] .svc-idx,
[dir="rtl"] .svc-chip { letter-spacing: 0; }
[dir="rtl"] .svc-chip { text-transform: none; }

.svc-title {
  margin: 0 0 8px;
  font-size: clamp(19px, 1.9vw, 22px);
  font-weight: 600;
  line-height: 1.3;
  color: var(--text-primary);
}
.svc-promise {
  margin: 0 0 18px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* Four bullets, always. Uniform length is what keeps the row even. */
.svc-list {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.svc-list li {
  position: relative;
  padding-inline-start: 16px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-secondary);
}
/* Same small accent square the case-study lists use. */
.svc-list li::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.55em;
  inline-size: 5px;
  block-size: 5px;
  background-color: var(--accent-blue);
  opacity: 0.55;
}

/* auto margin pins the foot and link to the bottom, so the three links
   align on one baseline whatever the copy above does (the notes that
   used to carry this margin were removed 2026-09-15). */
.svc-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 14px;
  margin-block-start: auto;
  padding-block-start: 18px;
}
.svc-meta { font-size: 12.5px; color: var(--text-tertiary); }
.svc-price {
  font-size: 13.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
.svc-price--figure {
  font-size: clamp(17px, 1.7vw, 20px);
  color: var(--accent-blue);
}
[data-theme="dark"] .svc-price--figure { color: var(--accent-blue-light); }

.svc-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  margin-block-start: 14px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: none;
  border-block-end: 1px solid var(--border-default);
  transition: color 0.2s ease, border-color 0.2s ease;
}
.svc-cta--primary { color: var(--accent-blue); border-block-end-color: currentColor; }
[data-theme="dark"] .svc-cta--primary { color: var(--accent-blue-light); }
.svc-cta:hover,
.svc-cta:focus-visible { color: var(--accent-blue); border-block-end-color: var(--accent-blue); }
[data-theme="dark"] .svc-cta:hover,
[data-theme="dark"] .svc-cta:focus-visible { color: var(--accent-blue-light); border-block-end-color: var(--accent-blue-light); }
/* .btn-arrow is collapsed to zero width at rest and only opens inside a
   .btn. These are text links, so the arrow is shown permanently and
   slides on hover. RTL keeps the mirror flip. */
.svc-cta .btn-arrow { width: 14px; opacity: 1; margin-inline-start: 0; transform: none; }
[dir="rtl"] .svc-cta .btn-arrow { transform: scaleX(-1); }
.svc-cta:hover .btn-arrow { transform: translateX(4px); }
[dir="rtl"] .svc-cta:hover .btn-arrow { transform: translateX(-4px) scaleX(-1); }

/* ── Design Partner ──
   Its own entry, deliberately not a fourth card and not part of the closing
   band. A wide, low row reads as a different kind of offer: not a project you
   buy once, but an arrangement that continues. Keeps a text link rather than a
   button so the section still has exactly one dark button on it. */
.svc-partner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px clamp(24px, 3vw, 48px);
  padding: clamp(22px, 2.4vw, 30px) clamp(24px, 2.6vw, 34px);
  border: 1px solid var(--border-default);
  border-radius: 20px;
  /* Matches .svc-card now: same gray fill as .work-panels, .skill-card
     and the Experience accordion. */
  background-color: var(--bg-secondary);
}
.svc-partner-text { flex: 1 1 460px; min-inline-size: 0; }
.svc-partner-label {
  display: block;
  margin-block-end: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
[dir="rtl"] .svc-partner-label { letter-spacing: 0; text-transform: none; }
.svc-partner-title {
  margin: 0 0 8px;
  font-size: clamp(17px, 1.8vw, 21px);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-primary);
}
.svc-partner-body {
  margin: 0;
  max-width: 72ch;
  font-size: 14px;
  line-height: 1.7;
  color: var(--text-secondary);
}
/* .svc-cta carries align-self: flex-start for the cards, which would pin
   this one to the top of a much taller row. */
.svc-partner .svc-cta { margin-block-start: 0; flex-shrink: 0; align-self: center; }

/* ── The closing band ──
   What used to be three stacked blocks. The split statement answers "who
   actually does the work" only after the offer has been seen, and the
   custom route sits beside it so the last thing on the section is a way
   in rather than a policy. */
.svc-close {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: clamp(20px, 2.5vw, 40px);
  /* Centered, not top-aligned: the split statement is now just two short
     paragraphs (the escalation line under it was cut 2026-09), so at
     align-items: start it sat noticeably shorter than the custom-route
     card beside it. Centering balances the two against the card's height
     instead of pinning both to the row's top edge. */
  align-items: center;
}
.svc-split {
  padding-inline-start: clamp(18px, 2vw, 26px);
  border-inline-start: 2px solid var(--accent-blue);
}
.svc-split-lead {
  margin: 0 0 10px;
  font-size: clamp(17px, 1.8vw, 21px);
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-primary);
}
.svc-split-body {
  margin: 0 0 10px;
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--text-secondary);
}
.svc-custom {
  padding: clamp(22px, 2.4vw, 30px);
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: 20px;
}
.svc-custom-title {
  margin: 0 0 6px;
  font-size: clamp(16px, 1.6vw, 18px);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-primary);
}
.svc-custom-body {
  margin: 0 0 18px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-secondary);
}

@media (max-width: 900px) {
  .svc-grid { grid-template-columns: 1fr; }
  .svc-close { grid-template-columns: 1fr; }
}

/* ============================================================
   SKILLS SECTION  (from Figma "SkillsSectionAlt")
   ============================================================ */
.skills-section {
  /* Same flat --bg-primary + dot-grid texture as the hero and every other
     section — one continuous page background. */
  position: relative;
  background-color: var(--bg-primary);
  padding: clamp(80px, 12vw, 160px) var(--container-pad);
}
.skills-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .skills-section::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

.skills-container {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.skills-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px 48px;
}

/* Entrance: cards fade/slide up as they scroll into view, staggered per
   card by js/skills.js via the --reveal-delay custom property. Decoupled
   from the hover transition below (animation, not transition) so the two
   never fight over the same `transform`/`opacity` properties. */
@keyframes skill-card-in {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}

.skill-card {
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  border-radius: 16px;
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  position: relative;
  overflow: hidden;
  opacity: 0;
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.3s ease,
              border-color 0.3s ease;
}

.skill-card.is-visible {
  animation: skill-card-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: var(--reveal-delay, 0ms);
}

/* Cursor-following spotlight glow (desktop only — set via JS on mousemove).
   Sits behind the header/tags via z-index below; a very light tint, so it
   doesn't touch text contrast, just adds a "lit from the cursor" feel. */
.skill-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(480px circle at var(--sk-x, 50%) var(--sk-y, 50%),
    rgba(59, 130, 246, 0.12), transparent 55%);
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

.skill-card:hover::before { opacity: 1; }

.skill-card:hover {
  transform: translateY(-4px);
  border-color: var(--accent-blue-light);
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.08);
}
[data-theme="dark"] .skill-card:hover {
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.4);
}

.skill-card-header {
  display: flex;
  align-items: center;
  gap: 12px;
  position: relative;
  z-index: 1;
}

.skill-card-line {
  display: block;
  width: 32px;
  height: 2px;
  border-radius: 2px;
  background-color: var(--accent-blue-light);
  flex-shrink: 0;
  transition: width 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.skill-card:hover .skill-card-line { width: 48px; }

.skill-card-title {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text-primary);
}

[dir="rtl"] .skill-card-title { letter-spacing: 0; }

.skill-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  position: relative;
  z-index: 1;
}

.skill-tag {
  display: inline-flex;
  align-items: center;
  height: 35px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  background-color: var(--surface-light-card);
  border: 1px solid var(--border-default);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.2px;
  color: var(--text-primary);
  white-space: nowrap;
  cursor: default;
  transition: background-color 0.25s ease,
              border-color 0.25s ease,
              color 0.25s ease,
              transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}

[dir="rtl"] .skill-tag { letter-spacing: 0; }

/* Tag hover: filled pill, dark blue (not accent-blue-light) — white text on
   accent-blue-light is only 3.68:1, under AA; on the darker accent-blue
   it's 6.7:1, which is why this uses the darker of the two blues. */
.skill-tag:hover {
  background-color: var(--accent-blue);
  border-color: var(--accent-blue);
  color: #fff;
  transform: translateY(-2px) scale(1.05);
}

/* Dark mode: an explicit lighten-overlay instead of another bg token —
   --surface-light-card and --bg-tertiary both resolve to the same dark
   value, which would make the tag invisible against its own card. */
[data-theme="dark"] .skill-tag {
  background-color: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
}
[data-theme="dark"] .skill-tag:hover {
  background-color: var(--accent-blue);
  border-color: var(--accent-blue);
}

/* ============================================================
   STUDIO BRIDGE SECTION
   Sits between Experience and Skills. Reuses .exp-header for the
   label/title/subtitle, but the panel itself is deliberately a DARK
   card in both themes (same local-variable-override technique as
   .site-footer below) — the studio is a different entity from the
   personal brand, and the tonal flip also breaks the run of identical
   light cards down the page.
   ============================================================ */
.studio-section {
  position: relative;
  background-color: var(--bg-primary);
  padding: clamp(80px, 12vw, 160px) var(--container-pad);
}
.studio-section::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.13) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}
[data-theme="dark"] .studio-section::before {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
}

.studio-container {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.studio-card {
  /* Locally redefined so the whole subtree renders dark in BOTH themes,
     exactly how .site-footer does it. --text-inverse is flipped too, so
     .btn-dark inside resolves to a light button with dark label. */
  --text-primary:   #F5F5F5;
  --text-secondary: #A3A3A3;
  --text-tertiary:  #8F8F8F;
  --text-inverse:   #0D0D0D;
  --border-default: rgba(255, 255, 255, 0.10);

  position: relative;
  overflow: hidden;
  isolation: isolate;
  background-color: var(--surface-dark-card);
  border: 1px solid var(--border-default);
  border-radius: 24px;
  padding: clamp(32px, 5vw, 64px);
  color: var(--text-primary);

  /* Single centered column. This card used to carry a second column (a
     numbered list, then a rail, then a tag cloud) naming the studio's
     wider range, but every itemized version of that list read as a menu
     of separate services — the fix that stuck was cutting the list
     entirely and letting .studio-lead's prose carry it alone. */
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* In dark mode the near-black panel (#080808) sat DARKER than the page
   background (#0D0D0D), so the card visually disappeared. Match the other
   cards on the page instead (--bg-secondary = #171717 in dark), which sits
   lighter than the page and reads as a card again. The light-mode
   near-black treatment is what gives the section its contrast, so that
   one stays. */
[data-theme="dark"] .studio-card {
  background-color: var(--bg-secondary);
}

/* Same dot-grid texture as the sections, dark variant, held inside the
   card's rounded corners. */
.studio-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.07) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}

/* Giant faint wordmark, the same motif the hero and footer already use,
   here naming the studio instead of the person. */
.studio-watermark {
  position: absolute;
  inset-inline-end: -0.05em;
  /* Sits just below the card edge so it reads as a bleed, not a full word.
     Paired with the mask below so the cut is a soft fade rather than a
     hard slice (the footer wordmark uses the same technique). */
  inset-block-end: -0.18em;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  font-size: clamp(130px, 19vw, 290px);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 0.75;
  color: #FFFFFF;
  opacity: 0.045;
  white-space: nowrap;
  -webkit-mask-image: linear-gradient(to bottom, #000 45%, transparent 92%);
          mask-image: linear-gradient(to bottom, #000 45%, transparent 92%);
}
/* Arabic script has descenders and rounded bowls that a hard bottom crop
   mangles (Latin caps have flat baselines and tolerate it). Lift the
   wordmark fully inside the card and size it down so نواة reads as a
   deliberate mark rather than a clipped glyph. */
[dir="rtl"] .studio-watermark {
  font-family: var(--font-arabic);
  letter-spacing: 0;
  inset-block-end: 0.02em;
  font-size: clamp(110px, 15vw, 230px);
  line-height: 1;
}

.studio-col {
  /* Centered: this used to sit beside a second column (a list naming the
     studio's wider range) and stayed left-aligned against it. That column
     is gone now — every itemized version of it read as a service menu,
     so .studio-lead's prose carries the point alone — and the card is a
     single centered column, so this centers with it. */
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  width: min(100%, 720px);
}

.studio-lead {
  font-size: 1.02rem;
  line-height: 1.65;
  color: var(--text-secondary);
  max-width: 60ch;
  text-align: center;
}

.studio-cta {
  margin-block-start: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px 22px;
  flex-wrap: wrap;
}

/* The outbound link to nawat.studio, demoted to secondary (2026-08-26).
   It used to be the section's only action, which sent the warmest visitor
   on the page to a second site. The primary action now stays here and
   this stays available as a text link beside it.
   ⚠️ .studio-card locally redefines --text-primary/--text-secondary so its
   subtree renders dark in BOTH themes, so these tokens already resolve to
   the light-on-dark values inside the card. Do not hard-code colours here
   or the link breaks the moment the card's palette changes. */
.studio-cta-secondary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
  border-block-end: 1px solid var(--border-default);
  transition: color 0.2s ease, border-color 0.2s ease;
}
.studio-cta-secondary:hover,
.studio-cta-secondary:focus-visible {
  color: var(--text-primary);
  border-block-end-color: var(--text-primary);
}
/* The box-arrow glyph points up-and-out, which reads as "leaves this site"
   in LTR. In RTL the whole layout mirrors, so mirror the glyph with it. */
[dir="rtl"] .studio-cta-secondary svg { transform: scaleX(-1); }

@media (max-width: 900px) {
  .studio-section { padding-block: 64px; }
  .studio-card {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 28px;
    border-radius: 20px;
  }
  .studio-watermark { font-size: clamp(110px, 34vw, 190px); }
}

/* ── Responsive ── */
@media (max-width: 900px) {
  .skills-section { padding-block: 64px; }
  .skills-grid { grid-template-columns: 1fr; gap: 16px; }
  .skill-card { padding: 24px; gap: 16px; }
  /* Longer entries (e.g. "Web & Mobile Development — Basic Understanding")
     need to wrap on mobile, but the desktop pill was a fixed 32px tall with
     nowrap text forced into it — once wrapped, that fixed height clipped
     the second line and squished it against the fully-rounded pill ends.
     Auto height + a smaller radius (still rounded, just not a capsule)
     fixes both: short tags still read as chips, long ones wrap cleanly
     into a soft rounded rectangle instead of an overflowing pill. */
  .skill-tag {
    font-size: 12.5px;
    height: auto;
    min-height: 32px;
    padding: 7px 14px;
    border-radius: var(--radius-lg);
    line-height: 1.4;
    white-space: normal;
    text-align: center;
  }
}

/* ============================================================
   FOOTER — always-dark "signature" closing section.
   Deliberately does NOT follow the light/dark toggle (matches the Figma
   source, which hardcodes a black footer regardless of theme) — it reads
   as a fixed closing statement for the page, like the intro overlay is a
   fixed opening one. Implemented by re-declaring the core tokens scoped
   to .site-footer with their dark-mode values, so every existing pattern
   reused inside it (pills, cards, arrows) resolves correctly with zero
   extra dark-mode overrides needed.
   ============================================================ */
.site-footer {
  --bg-primary:      #0D0D0D;
  --bg-secondary:    #171717;
  --bg-tertiary:     #1E1E1E;
  --text-primary:    #F5F5F5;
  --text-secondary:  #999999;
  --text-tertiary:   #8F8F8F;
  --border-default:  rgba(255, 255, 255, 0.08);

  position: relative;
  overflow: hidden;
  background-color: var(--bg-primary);
  color: var(--text-primary);
  padding: clamp(72px, 10vw, 120px) var(--container-pad) 0;
}

/* Shared dot-grid texture — always the dark variant since this section
   never follows the theme toggle. */
.site-footer::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.10) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}

/* Giant faint wordmark behind everything — echoes the hero's opening
   signature as a closing bookend. */
.footer-watermark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Lifted clear of the copyright bar (.footer-bottom, ~77px tall) instead
     of sitting flush with the section edge where it used to get cropped
     by overflow:hidden — the whole wordmark now reads as one deliberate
     shape instead of a sliver of letters peeking off the bottom. */
  padding-bottom: clamp(88px, 9vw, 112px);
  z-index: 0;
  pointer-events: none;
  user-select: none;
  overflow: hidden;
  /* Soft fade-in from the top so the giant letters don't end in a hard
     horizontal cutoff — a small creative touch instead of a flat crop. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 32%);
          mask-image: linear-gradient(to bottom, transparent, #000 32%);
}
.footer-watermark span {
  font-size: clamp(120px, 22vw, 320px);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 0.75;
  color: var(--text-primary);
  opacity: 0.05;
  white-space: nowrap;
}
[dir="rtl"] .footer-watermark span {
  font-family: var(--font-arabic);
  letter-spacing: 0;
}

.footer-inner {
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: clamp(56px, 7vw, 88px);
}

/* ── Intro: badge + heading + subtitle ── */
.footer-intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-align: center;
  max-width: 640px;
  margin-inline: auto;
}

.footer-badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.03em;
}

.footer-badge-dot {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--accent-green);
  flex-shrink: 0;
}
.footer-badge-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: var(--accent-green);
  animation: footer-pulse 2.2s ease-out infinite;
}
@keyframes footer-pulse {
  0%   { transform: scale(1);   opacity: 0.6; }
  100% { transform: scale(2.8); opacity: 0; }
}

.footer-heading {
  font-size: clamp(32px, 5vw, 56px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--text-primary);
}

.footer-subtitle {
  font-size: 16px;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* ── Scroll cue ──
   The footer intro is centered and tall, so on a short viewport (or a
   laptop at default zoom) it can fill the screen with nothing but the
   heading, leaving no sign the contact form is directly below. This is a
   pure affordance: decorative in the markup (aria-hidden), since a screen
   reader continues into the form anyway and has no such problem.
   No margin-block-start of its own, so it inherits .footer-intro's gap. */
.footer-scroll-cue {
  display: inline-flex;
  color: var(--text-tertiary);
  opacity: 0.75;
}

@media (prefers-reduced-motion: no-preference) {
  .footer-scroll-cue { animation: footer-cue-bob 2s ease-in-out infinite; }
}

/* Travels a short distance and rests at both ends, so it reads as a hint
   rather than a demand for attention. */
@keyframes footer-cue-bob {
  0%, 55%, 100% { transform: translateY(0); }
  25%           { transform: translateY(7px); }
}

/* ── Main: form + contacts, side by side ──
   Plain flex-direction:row auto-flips visual order under [dir="rtl"]
   (first DOM child lands on the right), matching the same convention
   already used for .hero — no reordering or RTL override needed here. */
.footer-main {
  display: flex;
  align-items: stretch;
  gap: clamp(32px, 5vw, 64px);
}

.footer-panel {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.footer-panel-title {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin-bottom: 24px;
}

/* ── Contact form ── */
.footer-form {
  display: flex;
  flex-direction: column;
  gap: 30px;
  flex: 1;
}

/* Project-type selector (shared by every footer form) */
.ff-ptype-field { margin-block-end: 24px; }
.ff-ptype-label { display: block; font-size: .82rem; font-weight: 600; color: var(--text-secondary); margin-block-end: 10px; }
.cs-ptype { display: flex; flex-wrap: wrap; gap: 8px; }
.cs-ptype-chip { font-family: inherit; font-size: .82rem; font-weight: 500; color: var(--text-secondary); padding: 7px 13px; border-radius: var(--radius-pill); border: 1px solid var(--border-default); background: var(--bg-primary); cursor: pointer; transition: background-color .2s ease, border-color .2s ease, color .2s ease, transform .25s cubic-bezier(0.34, 1.56, 0.64, 1); }
.cs-ptype-chip:hover { border-color: var(--accent-blue-light); transform: translateY(-1px); }
.cs-ptype-chip.is-active { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }

.ff-field {
  position: relative;
  padding-top: 10px;
  border-radius: var(--radius-md);
}

/* Cursor-following spotlight — same technique as .skill-card's mousemove
   glow (see js/skills.js), reused here via --ff-x/--ff-y in footer.js.
   Previews where the focus underline is about to draw in, so hovering a
   field already feels responsive before the user clicks into it. */
.ff-field::before {
  content: '';
  position: absolute;
  inset: 4px -14px -6px;
  border-radius: var(--radius-md);
  background: radial-gradient(140px circle at var(--ff-x, 50%) var(--ff-y, 50%),
    rgba(59, 130, 246, 0.16), transparent 70%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
  z-index: 0;
}
.ff-field:hover::before { opacity: 1; }

.ff-input {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border-default);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 16px;
  padding: 10px 0;
  text-align: start;
  caret-color: var(--accent-blue-light);
  transition: border-color 0.25s ease;
}
.ff-input:focus { outline: none; }
/* Hover brightens the resting track a touch — a quiet "this is
   interactive" cue distinct from the blue focus underline below. */
.ff-field:hover .ff-input { border-bottom-color: var(--text-tertiary); }
.ff-textarea {
  resize: vertical;
  min-height: 96px;
  line-height: 1.5;
}

.ff-label {
  position: absolute;
  z-index: 1;
  inset-inline-start: 0;
  top: 20px;
  font-size: 16px;
  color: var(--text-tertiary);
  pointer-events: none;
  transition: top 0.2s ease, font-size 0.2s ease, color 0.2s ease;
}
.ff-field:hover .ff-label { color: var(--text-secondary); }
.ff-input:focus ~ .ff-label,
.ff-input:not(:placeholder-shown) ~ .ff-label {
  top: -4px;
  font-size: 12px;
  color: var(--accent-blue-light);
}

.ff-underline {
  position: absolute;
  z-index: 1;
  bottom: 0;
  inset-inline: 0;
  height: 2px;
  background-color: var(--accent-blue-light);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.3s cubic-bezier(0.76, 0, 0.24, 1);
}
[dir="rtl"] .ff-underline { transform-origin: right center; }
.ff-input:focus ~ .ff-underline { transform: scaleX(1); }

.footer-submit {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding: 16px 30px;
  border: none;
  border-radius: var(--radius-pill);
  background-color: var(--accent-blue);
  color: #fff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
              box-shadow 0.25s ease,
              background-color 0.25s ease;
}
.footer-submit:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 24px rgba(29, 78, 216, 0.35);
}
.footer-submit-icon {
  display: inline-flex;
  align-items: center;
}
.footer-submit .icon-check { display: none; }
.footer-submit.is-sent {
  background-color: var(--accent-green);
}
.footer-submit.is-sent:hover {
  box-shadow: 0 10px 24px rgba(34, 197, 94, 0.35);
}
.footer-submit.is-sent .icon-send  { display: none; }
.footer-submit.is-sent .icon-check { display: inline-flex; }

.footer-submit:disabled {
  opacity: 0.7;
  cursor: not-allowed;
  transform: none !important;
}

/* Inline status text (sending / sent / error) — empty and invisible until
   JS sets content, so it never reserves visible space in the idle state */
/* ── Inline field errors, the message hint, the reply promise ──
   Errors replace the browser's native validation bubbles, which render in
   the browser's language rather than the site's and sit on the wrong side
   in RTL. */
.ff-error {
  display: none;
  margin-block-start: 6px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--accent-error);
}
.ff-error.is-visible { display: block; }
.ff-input.is-invalid ~ .ff-underline { background-color: var(--accent-error); }
.ff-input.is-invalid { border-block-end-color: var(--accent-error); }

/* A question rather than a label: it produces a message worth replying to
   instead of "hi, interested". */
.ff-hint {
  display: block;
  margin-block-start: 6px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-tertiary);
}

.ff-reply {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-tertiary);
}

.ff-status {
  margin: 10px 0 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-tertiary);
  min-height: 0;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.ff-status.is-visible { opacity: 1; }
.ff-status.is-error   { color: var(--accent-error); }

/* ── Direct contacts ── */
.footer-contacts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.footer-contact {
  position: relative;
}

/* Per-platform brand color, referenced by the hover states below */
.footer-contact--email      { --fc-brand: var(--accent-blue-light); }
.footer-contact--linkedin   { --fc-brand: #0A66C2; }
.footer-contact--whatsapp   { --fc-brand: #25D366; }
.footer-contact--instagram  { --fc-brand: #E1306C; }
.footer-contact--behance    { --fc-brand: #1769FF; }

.fc-link {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  border-radius: var(--radius-lg);
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-default);
  text-decoration: none;
  color: inherit;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
              border-color 0.25s ease,
              background-color 0.25s ease;
}
.footer-contact--email .fc-link {
  padding-inline-end: 58px; /* clears the floating copy button */
}
.fc-link:hover {
  transform: translateY(-2px);
  border-color: var(--fc-brand);
  background-color: color-mix(in srgb, var(--fc-brand) 10%, var(--bg-secondary));
}

.fc-icon {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(255, 255, 255, 0.07);
  color: var(--text-primary);
  transition: background-color 0.25s ease, color 0.25s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.fc-icon svg { width: 17px; height: 17px; }
.fc-link:hover .fc-icon {
  background-color: var(--fc-brand);
  color: #fff;
  transform: rotate(-8deg) scale(1.08);
}
/* WhatsApp's brand green is too light for a white icon to read clearly
   (1.98:1) — use a dark icon there instead (9.8:1). Every other brand
   color already clears the 3:1 non-text contrast minimum with white. */
.footer-contact--whatsapp .fc-link:hover .fc-icon {
  color: #0D0D0D;
}

.fc-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fc-label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.fc-value {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 0.25s ease;
}
.fc-link:hover .fc-value { color: var(--fc-brand); }

.fc-arrow {
  flex-shrink: 0;
  opacity: 0;
  color: var(--text-tertiary);
  transform: translateX(-6px);
  transition: opacity 0.25s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.fc-link:hover .fc-arrow { opacity: 1; transform: translateX(0); }
[dir="rtl"] .fc-arrow { transform: translateX(6px) scaleX(-1); }
[dir="rtl"] .fc-link:hover .fc-arrow { transform: translateX(0) scaleX(-1); }
.footer-contact--email .fc-arrow { display: none; }

/* Copy-email button — floats over the email row's end edge */
.fc-copy {
  position: absolute;
  inset-inline-end: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border-default);
  background-color: transparent;
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.fc-copy:hover {
  border-color: var(--accent-blue-light);
  color: var(--accent-blue-light);
}
.fc-copy .icon-copied { display: none; }
.fc-copy.is-copied {
  border-color: var(--accent-green);
  color: var(--accent-green);
}
.fc-copy.is-copied .icon-copy   { display: none; }
.fc-copy.is-copied .icon-copied { display: inline-flex; }

.fc-copy-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  inset-inline-end: 0;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent-green);
  background-color: var(--bg-tertiary);
  border: 1px solid var(--border-default);
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.fc-copy.is-copied .fc-copy-tip {
  opacity: 1;
  transform: translateY(0);
}

/* ── Bottom bar ── */
.footer-bottom {
  border-top: 1px solid var(--border-default);
  padding-block: 28px;
  display: flex;
  justify-content: center;
}
.footer-copyright {
  font-size: 13px;
  color: var(--text-tertiary);
  text-align: center;
}
.footer-copyright span + span { margin-inline-start: 4px; }

/* ── Scroll-in reveal — same fade/slide pattern as .skill-card-in ── */
.footer-reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--reveal-delay, 0ms);
}
.footer-reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── RTL: Arabic script reads slightly small at these sizes ── */
[dir="rtl"] .footer-badge        { font-size: 14.5px; letter-spacing: 0; }
[dir="rtl"] .footer-subtitle     { font-size: 17px; }
[dir="rtl"] .footer-panel-title  { font-size: 19.5px; }
[dir="rtl"] .ff-input,
[dir="rtl"] .ff-label            { font-size: 17px; }
[dir="rtl"] .ff-input:focus ~ .ff-label,
[dir="rtl"] .ff-input:not(:placeholder-shown) ~ .ff-label { font-size: 13px; }
[dir="rtl"] .footer-submit       { font-size: 16px; }
[dir="rtl"] .fc-label            { font-size: 12.5px; letter-spacing: 0; }
[dir="rtl"] .fc-value            { font-size: 16px; }

/* The WhatsApp number carries its own dir="ltr" (in index.html) so the
   digit groups always read in the correct order instead of getting
   bidi-reordered inside Arabic text. That isolates its direction from
   the page, which also changes what its default text-align resolves to
   — it was rendering centered instead of flush right like its label and
   every other contact value in the RTL layout. Force it back to right
   explicitly so only the digit order is special-cased, not the alignment. */
[dir="rtl"] .footer-contact--whatsapp .fc-value {
  text-align: right;
}
[dir="rtl"] .footer-copyright    { font-size: 14px; }

/* ── Responsive ── */
@media (max-width: 900px) {
  .site-footer { padding-top: 64px; }
  .footer-main { flex-direction: column; gap: 48px; }
  .footer-heading { font-size: clamp(28px, 7vw, 40px); }
}

@media (max-width: 640px) {
  .footer-inner { gap: 48px; }
  .footer-subtitle { font-size: 15px; }
  [dir="rtl"] .footer-subtitle { font-size: 16px; }
  .footer-submit { width: 100%; justify-content: center; }
  .fc-value { max-width: 46vw; }
}

@media (max-width: 480px) {
  .site-footer { padding-inline: 20px; }
  .footer-badge { font-size: 12px; text-align: center; }
  .fc-link { padding: 12px 14px; }
  .footer-contact--email .fc-link { padding-inline-end: 52px; }
}

/* ============================================================
   BACK TO TOP — floating button, shown once scrolled past the hero
   ============================================================ */
.back-to-top {
  position: fixed;
  right: 24px;
  bottom: calc(env(safe-area-inset-bottom) + 24px);
  z-index: 50; /* above page content, below navbar (100) and the
                  full-screen mobile menu (99) — sits behind the open
                  menu instead of floating awkwardly on top of it */
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background-color: var(--text-primary);
  color: var(--text-inverse);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px) scale(0.9);
  pointer-events: none;
  transition: opacity 0.3s ease,
              transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
              visibility 0.3s,
              background-color var(--transition-slow);
}
.back-to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}
.back-to-top:hover {
  transform: translateY(-3px) scale(1.05);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);
}
.back-to-top svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* Mirror to the opposite corner in Arabic, matching RTL reading direction */
[dir="rtl"] .back-to-top {
  right: auto;
  left: 24px;
}

@media (max-width: 560px) {
  .back-to-top {
    right: 16px;
    bottom: calc(env(safe-area-inset-bottom) + 16px);
    width: 42px;
    height: 42px;
  }
  [dir="rtl"] .back-to-top {
    right: auto;
    left: 16px;
  }
}

/* Respects reduced-motion: fade only, no spring/slide */
@media (prefers-reduced-motion: reduce) {
  .back-to-top {
    transition: opacity 0.2s ease, visibility 0.2s;
    transform: none;
  }
  .back-to-top:hover {
    transform: none;
  }
}


/* ═══════════════════════════════════════════════════════════
   CV: footer item and preview modal
   ═══════════════════════════════════════════════════════════ */

/* Footer CV item — a <button> that visually matches the .fc-link anchors.
   Keep the card styling from .fc-link; only normalise button quirks. */
.footer-contact--cv { --fc-brand: var(--accent-blue); }
.fc-link--btn {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
}

/* ── Modal ── */
.cv-modal[hidden] { display: none; }
.cv-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 4vw, 48px);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.cv-modal.is-open { opacity: 1; }

.cv-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

.cv-modal__dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(880px, 100%);
  height: min(88vh, 1040px);
  background: var(--bg-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35);
  overflow: hidden;
  transform: translateY(14px) scale(0.98);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.cv-modal.is-open .cv-modal__dialog { transform: none; }

.cv-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-default);
}
.cv-modal__title {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0;
  line-height: 1.2;
}
.cv-modal__sub {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 3px 0 0;
}
.cv-modal__close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}
.cv-modal__close:hover { background: var(--bg-tertiary); transform: rotate(90deg); }

.cv-modal__body {
  flex: 1;
  min-height: 0;
  background: var(--bg-secondary);
}
.cv-modal__frame {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.cv-modal__foot {
  display: flex;
  gap: 12px;
  padding: 16px 20px;
  border-top: 1px solid var(--border-default);
  flex-wrap: wrap;
}

@media (max-width: 760px) {
  .cv-modal__dialog { height: 92vh; }
}

/* Respect reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .cv-modal, .cv-modal__dialog { transition: none; }
  .cv-modal__close:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════
   Hero scroll cue — mouse-style track with a dropping dot
   ═══════════════════════════════════════════════════════════ */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(16px, 3vh, 32px);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--text-tertiary);
  opacity: 1;
  transition: opacity 0.45s ease, color 0.25s ease, transform 0.35s ease;
  z-index: 3;
}
.scroll-cue:hover { color: var(--text-primary); }

.scroll-cue__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

[dir="rtl"] .scroll-cue__label {
  font-size: 12.5px;
  letter-spacing: 0;
}

.scroll-cue__track {
  position: relative;
  width: 22px;
  height: 34px;
  border: 1.5px solid currentColor;
  border-radius: 12px;
  opacity: 0.65;
}
.scroll-cue__dot {
  position: absolute;
  left: 50%;
  top: 7px;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: currentColor;
  animation: scrollCueDot 1.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes scrollCueDot {
  0%   { transform: translateY(0);    opacity: 0; }
  22%  { opacity: 1; }
  68%  { transform: translateY(13px); opacity: 0; }
  100% { transform: translateY(13px); opacity: 0; }
}

/* Fades out (and drifts down) once the visitor scrolls */
.scroll-cue.is-hidden {
  opacity: 0;
  pointer-events: none;
  transform: translateX(-50%) translateY(10px);
}

/* On phones the hero is usually taller than the screen, so pin the cue to
   the viewport bottom (not the hero's) and slim it to just the animated
   track. It still fades out on scroll via JS. */
@media (max-width: 768px) {
  .scroll-cue {
    position: fixed;
    bottom: calc(env(safe-area-inset-bottom) + 12px);
    gap: 0;
  }
  .scroll-cue__label { display: none; }
  .scroll-cue__track { width: 20px; height: 31px; opacity: 0.5; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue__dot { animation: none; top: 50%; margin-top: -2px; }
  .scroll-cue { transition: opacity 0.3s ease; }
}
