/* =====================================================================
   Photographer Studio - Public Site Stylesheet
   Design system: CSS custom properties drive both light & dark themes.
   ===================================================================== */

/* Custom display fonts (self-hosted, not Google Fonts): Impact.ttf for
   English headings/branding, SalmaArabic-Bold.otf for Arabic ones. Both
   are single-weight files, so font-weight below is set to the weight the
   file actually is (rather than 400/normal) - requesting a weight the
   file doesn't have would make the browser synthesize a fake bold/regular
   instead of showing the real letterforms. font-display: swap avoids a
   render-blocking flash of invisible text while either file loads. */
@font-face {
    font-family: 'Impact';
    src: url('../fonts/Impact.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'SalmaArabic';
    src: url('../fonts/SalmaArabic-Bold.otf') format('opentype');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

:root {
    --gold: #228dff;
    --gold-dark: #0061c9;
    --ink: #111318;
    --bg: #ffffff;
    --bg-alt: #f0f6fe;
    --surface: #ffffff;
    --text: #1c1e22;
    --text-muted: #6b7078;
    --border: #e6e6e6;
    --shadow: 0 10px 30px rgba(20, 20, 20, 0.06);
    --radius: 16px;
    /* --font-heading drives every headline, the shrinking hero name, the
       navbar brand, stat numbers and package prices (all already reference
       this one variable) - repointing it here to Impact, with the
       lang="ar" override just below swapping in SalmaArabic-Bold, updates
       all of those at once with no risk of missing a selector. --font-body
       (running/paragraph text) is left on Poppins/Tajawal: both supplied
       files are heavy, single-weight display faces meant for short
       headlines, not for setting paragraphs. */
    --font-heading: 'Impact', 'Arial Narrow', 'Franklin Gothic Medium', sans-serif;
    --font-body: 'Poppins', 'Segoe UI', sans-serif;
    --transition: 0.35s cubic-bezier(.4,0,.2,1);
}

html[lang="ar"] {
    --font-heading: 'SalmaArabic', 'Tajawal', sans-serif;
}

[data-theme="dark"] {
    --ink: #f5f3ef;
    --bg: #0e0f12;
    --bg-alt: #16181d;
    --surface: #1a1c22;
    --text: #eceef1;
    --text-muted: #9a9fa8;
    --border: #2a2d34;
    --shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

/* Bootstrap's own .text-muted (from its _utilities.scss) is set from
   --bs-secondary-color, a fixed rgba(mostly-black) value that Bootstrap
   itself only re-themes under its own data-bs-theme="dark" attribute -
   which this site doesn't set, since it drives dark mode entirely through
   this custom [data-theme="dark"] attribute/CSS-variable system instead.
   Left alone, .text-muted stays a dark gray in dark mode too, nearly
   unreadable against our dark backgrounds (service descriptions,
   testimonial quotes, and the footer's contact details all use it). This
   repoints it at our own --text-muted, which the [data-theme="dark"] block
   above already gives a light-enough value; any icon nested inside a
   .text-muted element (e.g. the footer's address pin) inherits this same
   color automatically via currentColor, no separate icon rule needed. */
[data-theme="dark"] .text-muted { color: var(--text-muted) !important; }

html[lang="ar"] body { font-family: 'Tajawal', var(--font-body); }

* { box-sizing: border-box; }

/* overflow-x lives on <html>, not <body>. Mobile Safari's rendering engine
   treats a <body> with its own overflow as a scrolling container distinct
   from the page/viewport, and `position: fixed` descendants (like #mainNav)
   are then positioned/repainted relative to that container instead of the
   real viewport — the browser can lose track of it, so it intermittently
   fails to render until the next full repaint (e.g. right after the language
   switcher reloads the page). Setting it on <html> avoids creating that
   second scrolling context, so the fixed navbar reliably stays put and
   visible. */
html { overflow-x: hidden; }

body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-body);
    transition: background var(--transition), color var(--transition);
}

body.lang-fade { animation: langFade .5s ease; }
@keyframes langFade { from { opacity: 0; } to { opacity: 1; } }

h1, h2, h3, h4, .display-font { font-family: var(--font-heading); color: var(--ink); }

a { color: var(--gold-dark); text-decoration: none; }
a:hover { color: var(--gold); }

.section { padding: 100px 0; }
@media (max-width: 767px) { .section { padding: 64px 0; } }

.section-eyebrow {
    color: var(--gold-dark);
    letter-spacing: 3px;
    text-transform: uppercase;
    font-weight: 600;
    font-size: .8rem;
}

.section-heading { font-size: clamp(1.8rem, 4vw, 2.6rem); margin-bottom: .5rem; }
.section-sub { color: var(--text-muted); max-width: 640px; margin: 0 auto; }
.text-center-block { text-align: center; margin-bottom: 48px; }

.btn-gold {
    background: linear-gradient(135deg, var(--gold), var(--gold-dark));
    border: none;
    /* White text/icons for anything sitting on a solid --gold/--gold-dark
       fill - the near-black #17140a this used to be was tuned for the old
       warm gold and reads poorly against the current blue. */
    color: #fff;
    font-weight: 600;
    padding: 12px 30px;
    border-radius: 999px;
    transition: transform var(--transition), box-shadow var(--transition);
    box-shadow: 0 8px 20px rgba(34,141,255,.35);
}
.btn-gold:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(34,141,255,.45); color: #fff; }

.btn-outline-soft {
    border: 1px solid var(--border);
    color: var(--text);
    padding: 12px 30px;
    border-radius: 999px;
    background: transparent;
    transition: all var(--transition);
}
.btn-outline-soft:hover { border-color: var(--gold); color: var(--gold-dark); }

/* ---------------- Navbar ---------------- */
#mainNav {
    background: transparent;
    padding: 20px 0;
    transition: background var(--transition), padding var(--transition), box-shadow var(--transition);
    position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
    /* Forces the fixed navbar onto its own compositor layer. Without this,
       mobile Safari can intermittently fail to repaint a `position: fixed`
       element in place — it can flicker, vanish, or lag behind while
       scrolling or right after a full page load (e.g. after the language
       switcher reloads the page) — because the browser is still treating it
       as part of the normal scrolling layer instead of a pinned one. */
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
}
#mainNav.scrolled {
    background: var(--surface);
    padding: 10px 0;
    box-shadow: var(--shadow);
}
@media (max-width: 991.98px) {
    /* Below the lg breakpoint the nav-links live in the collapsible mobile
       menu. While it's open, force the same solid, theme-aware background
       the scrolled navbar uses (even at scroll position 0), so the menu text
       never sits on top of - and blends into - the hero content behind it. */
    #mainNav.menu-open {
        background: var(--surface);
        box-shadow: var(--shadow);
    }
}
/* The brand name starts hidden at every viewport width and only fades/
   slides into view once the large #heroName in the hero has scrolled up to
   reach the fixed navbar (see the "HeroName shrink-into-navbar" block in
   main.js, which also continuously shrinks #heroName itself as it
   approaches that point) — together they make it look like the hero's name
   itself travels up and shrinks into the navbar, rather than two unrelated
   pieces of text swapping visibility. */
#mainNav .navbar-brand {
    font-family: var(--font-heading); font-weight: 700; font-size: 1.4rem; color: var(--ink) !important;
    opacity: 0;
    transform: translateY(-14px);
    transition: opacity .4s ease, transform .4s ease;
    pointer-events: none;
}
#mainNav .navbar-brand.brand-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
#mainNav .nav-link { color: var(--text) !important; font-weight: 500; margin: 0 8px; position: relative; }
#mainNav .nav-link::after {
    content: ''; position: absolute; bottom: -4px; left: 0; width: 0; height: 2px;
    background: var(--gold); transition: width var(--transition);
}
#mainNav .nav-link:hover::after, #mainNav .nav-link.active::after { width: 100%; }

/* Animated hamburger -> X. Three bars, rotated/faded into an X once the menu
   is open; Bootstrap keeps aria-expanded on this button in sync with the
   collapse's actual state, so it alone is enough to drive the animation. */
.navbar-toggler {
    width: 40px; height: 40px;
    padding: 6px;
    border: none;
    border-radius: 50%;
    background: transparent;
    box-shadow: none !important;
    display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
}
.navbar-toggler:focus { box-shadow: none; }
.toggler-bar {
    display: block;
    width: 22px; height: 2px;
    border-radius: 2px;
    background: var(--ink);
    transition: transform .35s cubic-bezier(.4,0,.2,1), opacity .25s ease, background .35s ease;
}
.navbar-toggler[aria-expanded="true"] .toggler-bar-1 { transform: translateY(8px) rotate(45deg); }
.navbar-toggler[aria-expanded="true"] .toggler-bar-2 { opacity: 0; }
.navbar-toggler[aria-expanded="true"] .toggler-bar-3 { transform: translateY(-8px) rotate(-45deg); }

.icon-btn {
    width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--border);
    background: var(--surface); color: var(--text); display: inline-flex; align-items: center; justify-content: center;
    transition: all var(--transition); cursor: pointer;
}
.icon-btn:hover { border-color: var(--gold); color: var(--gold-dark); transform: rotate(15deg); }

/* ---------------- Hero (now also carries the About content right below it,
   inside the same <section id="home"> - see index.php. .hero itself just
   supplies the shared background/pattern for the whole merged section;
   the full-viewport centered intro is scoped to .hero-intro, its first
   child, so About can flow in normal document flow underneath it rather
   than being squeezed into that same 100vh flexbox. ---------------- */
.hero {
    position: relative;
    background: radial-gradient(circle at 80% 20%, rgba(34,141,255,.15), transparent 55%), var(--bg-alt);
    overflow: hidden;
}
.hero::before {
    content: ''; position: absolute; inset: 0;
    background-image: radial-gradient(circle, var(--border) 1px, transparent 1px);
    background-size: 26px 26px; opacity: .35; pointer-events: none;
}
.hero-intro {
    min-height: 100vh;
    display: flex;
    align-items: center;
    position: relative;
}
.hero-photo-frame {
    position: relative; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow);
    aspect-ratio: 4/5;
}
.hero-photo-frame img { width: 100%; height: 100%; object-fit: cover; }
.hero h1 { font-size: clamp(2.4rem, 6vw, 4rem); line-height: 1.1; }
.hero .lead-muted { color: var(--text-muted); font-size: 1.15rem; max-width: 520px; }
@media (max-width: 767px) {
    /* The fixed #mainNav otherwise sits on top of the hero's own top edge on
       mobile, covering the eyebrow/name. Push the hero content down by the
       navbar's mobile height so nothing starts underneath it. */
    .hero { padding-top: 90px; }
    /* On mobile the two hero columns stack (Bootstrap's own behavior below
       lg), and everything in them - name, bio, the two CTA buttons, and the
       photo - is centered as a single column instead of left/right-aligned,
       per request. `.lead-muted` keeps its `max-width` (so lines don't run
       edge-to-edge) but needs an explicit auto margin to actually center
       that narrower block within the full-width column - text-align alone
       only centers the text *inside* it. */
    .hero-intro .col-lg-6:first-child { text-align: center; }
    .hero-intro .lead-muted { margin-left: auto; margin-right: auto; }
    .hero-intro .hero-cta-row { justify-content: center; }
    /* The photo is a portrait card (not edge-to-edge) on mobile so it reads
       as a centered element alongside the centered text above it, rather
       than a full-bleed image breaking the centered column's rhythm. */
    .hero-photo-frame { max-width: 320px; margin: 0 auto; }
}
/* #heroName's own continuous scroll-driven scale (applied in main.js) needs
   a fixed anchor point to shrink toward, rather than shrinking evenly from
   its own center, so it reads as sliding/shrinking up into the navbar
   brand's corner instead of just shrinking in place. Anchored to the
   reading-start corner (left in LTR, right in RTL) since that's roughly
   where both #heroName and the navbar brand sit horizontally. */
#heroName { transform-origin: top left; will-change: transform; }
[dir="rtl"] #heroName { transform-origin: top right; }
@media (prefers-reduced-motion: reduce) {
    /* main.js itself checks this and skips the whole scroll-driven effect,
       but this is the actual fallback appearance: the hero name stays full
       size, and the navbar brand is simply always visible rather than
       gated behind a scroll position it will never move for. */
    #heroName { transform: none !important; }
    #mainNav .navbar-brand {
        opacity: 1 !important;
        transform: none !important;
        pointer-events: auto !important;
        transition: none !important;
    }
}
/* ---------------- About (merged into #home, see above) ---------------- */
.stat-box { text-align: center; padding: 20px; }
.stat-box .num { font-family: var(--font-heading); font-size: 2.2rem; color: var(--gold-dark); font-weight: 700; }
.stat-box .label { color: var(--text-muted); font-size: .9rem; }
/* Stats now live inside the hero's half-width intro column (see index.php),
   a tighter space than the old full-width About row they came from -
   smaller padding/type keeps three of them from feeling cramped there. */
.hero-intro .stat-box { padding: 8px 4px; }
.hero-intro .stat-box .num { font-size: 2.7rem; }
.hero-intro .stat-box .label { font-size: .78rem; }

/* ---------------- Services (scroll-path animation) ----------------
   Each .service-frame is a fixed, short slice of scroll travel (260px, not
   viewport-relative - deliberately small so the whole section costs very
   little scrolling to get through). The card inside it sits centered by
   default (this is its resting, "no transform" state); main.js reads the
   frame's own position within the viewport every scroll frame and applies
   a transform tracing a half-circle to the card: off-screen and invisible
   at the start/end of that travel, full scale/opacity/centered exactly
   when the frame's center reaches the viewport's center. Progress is still
   measured against the full viewport height (not the frame's own much
   shorter height), so consecutive cards' arcs deliberately overlap for
   much of the scroll instead of handing off at a single instant - by the
   time one card is centered, the next is often already partway into its
   own entrance. That's an intentional trade for such a short section: it
   guarantees the opposite failure mode never happens (there's no scroll
   position where every card is invisible), at the cost of a clean one-at-
   a-time handoff. On top of the card's own translate/scale/fade, main.js
   also spins just the .service-icon (not the title/description) through
   the same arc's angle, so the icon reads as the part tracing the curve
   while the text next to it stays upright and legible throughout. Unlike
   the arc's vertical motion, the horizontal motion is symmetric around
   center (same offset magnitude AND side at both ends) rather than
   antisymmetric, so a card enters from and exits back toward the *same*
   side of the screen instead of passing through to the opposite side.
   overflow-x:hidden keeps that horizontal swing (which deliberately goes
   off-screen) from ever creating a page scrollbar. */
.services-scroll { overflow-x: hidden; }
.service-frame {
    height: 260px; display: flex; align-items: center; justify-content: center;
    position: relative;
}
.service-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 28px 34px; width: 100%; max-width: 620px;
    display: flex; align-items: center; gap: 22px;
    will-change: transform, opacity;
}
.service-icon {
    flex: 0 0 auto;
    width: 108px; height: 108px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, rgba(34,141,255,.18), rgba(34,141,255,.05));
    color: var(--gold-dark); font-size: 3rem;
    will-change: transform;
}
.service-text { min-width: 0; }
.service-text h4 { margin-bottom: 6px; font-size: 1.7rem; }
.service-text p { font-size: 1.08rem; line-height: 1.5; margin-bottom: 0; }
@media (max-width: 575.98px) {
    /* Narrow phones: the icon+text row gets tight fast at 108px - shrink
       both a step so the card doesn't force horizontal crowding or wrap
       awkwardly, while staying well above the pre-redesign 64px icon. */
    .service-card { gap: 16px; padding: 22px 20px; }
    .service-icon { width: 80px; height: 80px; font-size: 2.2rem; }
    .service-text h4 { font-size: 1.35rem; }
    .service-text p { font-size: .95rem; }
}
/* prefers-reduced-motion: main.js itself checks this and skips applying
   any transform/opacity/rotation at all when set, but this rule is the
   actual fallback appearance those untouched cards get - centered, full
   size, fully visible, just without the scroll-linked motion. */
@media (prefers-reduced-motion: reduce) {
    .service-card { opacity: 1 !important; transform: none !important; }
    .service-icon { transform: none !important; }
}

/* ---------------- Gallery ---------------- */
.filter-bar { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-bottom: 0; }
.filter-btn {
    border: 1px solid var(--border); background: var(--surface); color: var(--text);
    padding: 8px 20px; border-radius: 999px; font-size: .9rem; cursor: pointer; transition: all var(--transition);
}
.filter-btn.active, .filter-btn:hover { background: var(--gold); border-color: var(--gold); color: #fff; }

/* Toolbar stack: filter pills on top, the slider/grid view switcher
   centered directly beneath them on its own row (previously sat beside the
   filters on one row). */
.gallery-toolbar { display: flex; flex-direction: column; align-items: center; gap: 18px; margin-bottom: 40px; }

/* Segmented-control look: a bordered "track" holding both buttons, with the
   active one lifted onto a solid gold background - reads clearly as a
   two-way switch rather than two plain, easy-to-miss icon buttons. */
.gallery-view-toggle {
    display: flex; gap: 4px; flex-shrink: 0;
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 4px;
}
.gallery-view-btn {
    width: 44px; height: 40px; border-radius: 10px; border: none;
    background: transparent; color: var(--text-muted); display: flex; align-items: center; justify-content: center;
    cursor: pointer; transition: all var(--transition); font-size: 1.05rem;
}
.gallery-view-btn:hover { color: var(--text); background: var(--bg-alt); }
.gallery-view-btn.active { background: var(--gold); color: #fff; box-shadow: 0 6px 16px rgba(0,0,0,.18); }
.gallery-view-btn.active:hover { background: var(--gold); color: #fff; }

/* View switching: main.js toggles a `view-slider` class on #galleryViews.
   Grid is the default view (no class needed to see it); adding the class
   swaps to the coverflow slider instead. Both views' markup is always in
   the DOM (they share one GLightbox instance/GALLERY_DATA array - see
   index.php), only visibility changes, so flipping back and forth never
   rebuilds or refetches anything. */
.gallery-views .coverflow-wrap { display: none; }
.gallery-views.view-slider .coverflow-wrap { display: block; }
.gallery-views.view-slider .gallery-grid { display: none; }

/* ---------------- Gallery Coverflow ---------------- */
.coverflow-wrap { position: relative; }
.coverflow {
    position: relative;
    min-height: 320px;
    margin: 0 auto 8px;
    max-width: 100%;
    /* The mask-image only fades pixels that are already visible - it does not
       clip anything. Without overflow:hidden, the off-center .coverflow-item
       cards (positioned absolutely and pushed left/right of center) keep
       extending the page's real layout width well past the edge of the
       screen on mobile, which is exactly the kind of horizontal overflow
       that breaks `position: fixed` rendering for #mainNav on real mobile
       browsers - the fixed navbar stops sticking, scrolls with the page, and
       only "catches up" when the browser recalculates on scroll direction
       change. Clipping here keeps the whole document's width honest. */
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    cursor: grab;
    touch-action: pan-y;
    user-select: none;
}
.coverflow.dragging { cursor: grabbing; }
.coverflow-item {
    position: absolute;
    top: 50%; left: 50%;
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 18px 40px rgba(20,20,20,.16);
    /* No transform/opacity transition here on purpose: main.js now drives
       position frame-by-frame via requestAnimationFrame (drag-follow, flick
       momentum, chevron-hold scroll, and the eased single-step moves all
       need direct per-frame control), and layering a CSS transition on top
       of that would fight it and make everything feel laggy/double-animated.
       box-shadow is the one property still fine to transition on its own. */
    transition: box-shadow .4s ease;
    will-change: transform, opacity;
}
/* Without this, Chrome/Safari treat the <a>/<img> inside as a native draggable
   link/image: a mousedown-then-move on it kicks off the browser's own drag-out
   gesture, which swallows the pointermove/pointerup events our swipe handler
   below needs. The `draggable="false"` HTML attribute on those elements is the
   primary fix; these properties are the CSS-level backstop for any browser
   that still tries. */
.coverflow-item a,
.coverflow-item img {
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-user-select: none;
    user-select: none;
}
.coverflow-item.is-center { box-shadow: 0 30px 60px rgba(20,20,20,.3); cursor: pointer; }
.coverflow-item:not(.is-center) { cursor: pointer; }
.coverflow-item a { display: block; width: 100%; height: 100%; position: relative; }
.coverflow-item img, .coverflow-item video { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
/* Slider-only caption (title/type/location), revealed for whichever item
   is centered. The grid view intentionally shows no text under its
   thumbnails - see .gallery-grid below - so this stays scoped to
   .coverflow-item. */
.coverflow-item .coverflow-caption {
    position: absolute; inset: auto 0 0 0; padding: 20px 18px;
    background: linear-gradient(0deg, rgba(0,0,0,.78), transparent 60%);
    color: #fff; opacity: 0; transition: opacity .35s ease; pointer-events: none;
}
.coverflow-item.is-center .coverflow-caption { opacity: 1; }
.coverflow-item .play-badge,
.gallery-grid-item .play-badge {
    position: absolute; top: 12px; right: 12px; background: rgba(0,0,0,.55); color: #fff;
    width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    pointer-events: none;
}
/* Slightly smaller play badge on the grid, since its tiles can run smaller
   than the slider's - keeps the badge from ever overwhelming a tile. Parked
   at the opposite corner from .grid-overlay-expand below so a video tile's
   two top-corner icons never collide. */
.gallery-grid-item .play-badge { width: 30px; height: 30px; font-size: .8rem; top: 8px; left: 8px; right: auto; }
[dir="rtl"] .gallery-grid-item .play-badge { left: auto; right: 8px; }

/* ---------------- Gallery Grid (default view) ----------------
   Modeled on 500px.com's own photo grid: uniform square tiles packed
   edge-to-edge with a thin 3px gutter (not isolated thumbnails with room
   around them), auto-filling the full container width so the column count
   adapts to the viewport instead of being hardcoded per breakpoint. Every
   tile crops its image to fill the square (object-fit:cover) rather than
   preserving its own aspect ratio, which is what makes a photo grid this
   tight actually line up - a mix of portrait/landscape photos in
   uncropped tiles of the same width would leave ragged row heights and
   gaps, the exact thing a tight justified/grid layout avoids. */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 3px;
}
.gallery-grid-item {
    display: block;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    position: relative;
    cursor: pointer;
}
.gallery-grid-item a { display: block; position: relative; width: 100%; height: 100%; }
.gallery-grid-item img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    transition: transform .5s ease;
}
/* 500px's own hover treatment: the photo itself slowly zooms in behind a
   dark gradient that fades up from the bottom, surfacing the title/
   category and a small expand icon - info that only ever appears on top
   of the image on hover, never as text sitting under the tile. */
.gallery-grid-item:hover img, .gallery-grid-item:focus-within img { transform: scale(1.08); }
.gallery-grid-item .grid-overlay {
    position: absolute; inset: 0;
    background: linear-gradient(0deg, rgba(0,0,0,.75) 0%, rgba(0,0,0,.15) 45%, transparent 70%);
    opacity: 0; transition: opacity .3s ease;
    display: flex; flex-direction: column; justify-content: flex-end;
    padding: 14px; color: #fff; pointer-events: none;
}
.gallery-grid-item:hover .grid-overlay, .gallery-grid-item:focus-within .grid-overlay { opacity: 1; }
.gallery-grid-item .grid-overlay-expand {
    position: absolute; top: 10px; right: 10px;
    width: 30px; height: 30px; border-radius: 50%;
    background: rgba(255,255,255,.18); backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center; font-size: .75rem;
}
[dir="rtl"] .gallery-grid-item .grid-overlay-expand { right: auto; left: 10px; }
@media (max-width: 575.98px) {
    .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 2px; }
    .gallery-grid-item .grid-overlay { padding: 8px; }
}
@media (prefers-reduced-motion: reduce) {
    .gallery-grid-item img { transition: none; }
    .gallery-grid-item:hover img, .gallery-grid-item:focus-within img { transform: none; }
}

/* ---------------- Instagram horizontal auto-scroll strip ----------------
   Admin-managed photos (Admin -> Instagram Photos) rendered as an
   infinitely-looping horizontal marquee rather than a static grid. The
   PHP side (index.php) renders the photo list twice back-to-back inside
   .instagram-scroll-track; the keyframe animation translates the track by
   exactly -50% (one copy's width), so the moment the first copy has fully
   scrolled off, the identical second copy is sitting exactly where the
   first one started - the loop reset is invisible. A soft left/right mask
   fades the strip into the section background instead of hard-cutting
   tiles at the edge. */
.instagram-scroll-wrap {
    overflow: hidden;
    position: relative;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 4%, #000 96%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 4%, #000 96%, transparent);
}
.instagram-scroll-track {
    display: flex;
    width: max-content;
    gap: 16px;
    animation: instagram-scroll 45s linear infinite;
}
/* Pause on hover/focus (mouse) and on touch (:active) so a visitor can
   actually read a caption or line up a tap before the strip moves on. */
.instagram-scroll-wrap:hover .instagram-scroll-track,
.instagram-scroll-wrap:focus-within .instagram-scroll-track,
.instagram-scroll-wrap:active .instagram-scroll-track {
    animation-play-state: paused;
}
@keyframes instagram-scroll {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}
/* [dir="rtl"] intentionally does NOT reverse the scroll direction - the
   loop math (-50%) depends on the track's natural (LTR) layout direction,
   and reversing it here would require mirroring the whole keyframe too. */
.instagram-scroll-item {
    flex: 0 0 auto;
    width: 240px;
    aspect-ratio: 1 / 1;
    border-radius: 14px;
    overflow: hidden;
    position: relative;
    cursor: pointer;
}
.instagram-scroll-item a { display: block; position: relative; width: 100%; height: 100%; }
.instagram-scroll-item img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    transition: transform .5s ease;
}
.instagram-scroll-item:hover img, .instagram-scroll-item:focus-within img { transform: scale(1.08); }
.instagram-scroll-item .instagram-overlay {
    position: absolute; inset: 0;
    background: rgba(0,0,0,.55);
    opacity: 0; transition: opacity .3s ease;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    color: #fff; pointer-events: none; text-align: center; padding: 10px;
}
.instagram-scroll-item:hover .instagram-overlay, .instagram-scroll-item:focus-within .instagram-overlay { opacity: 1; }
.instagram-scroll-item .instagram-overlay-icon { font-size: 1.4rem; }
.instagram-scroll-item .instagram-overlay-text { font-size: .8rem; font-weight: 500; }
/* Caption sits under the overlay's own text, only shown when a photo has
   one - own small gradient strip so it reads over any image. */
.instagram-scroll-item .instagram-scroll-caption {
    position: absolute; inset: auto 0 0 0; padding: 10px 12px 8px;
    background: linear-gradient(0deg, rgba(0,0,0,.75), transparent 100%);
    color: #fff; font-size: .78rem; font-weight: 500;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    opacity: 0; transition: opacity .3s ease; pointer-events: none;
}
.instagram-scroll-item:hover .instagram-scroll-caption, .instagram-scroll-item:focus-within .instagram-scroll-caption { opacity: 1; }
@media (max-width: 575.98px) {
    .instagram-scroll-item { width: 150px; border-radius: 10px; }
    .instagram-scroll-track { gap: 10px; }
}
/* A visitor who prefers reduced motion gets a plain manually-scrollable
   strip instead of the auto-playing loop - not a static grid, since
   "horizontally scrolled" is still the intended layout, just under the
   visitor's own control rather than animated. */
@media (prefers-reduced-motion: reduce) {
    .instagram-scroll-track { animation: none; }
    .instagram-scroll-wrap { overflow-x: auto; }
    /* Hide the second (aria-hidden) copy used for the seamless loop - with
       the animation off there's no loop to hide, so showing it would just
       duplicate every photo in the manually-scrollable list. */
    .instagram-scroll-item[aria-hidden="true"] { display: none; }
    .instagram-scroll-item img { transition: none; }
    .instagram-scroll-item:hover img, .instagram-scroll-item:focus-within img { transform: none; }
}

/* Side chevrons: siblings of #coverflow (not inside it), positioned over its
   left/right edges. Kept outside the drag surface so pressing one is never
   mistaken for the start of a swipe. */
.coverflow-chevron {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--border);
    background: var(--surface); color: var(--text); display: flex; align-items: center; justify-content: center;
    cursor: pointer; transition: opacity .25s ease, border-color .25s ease, color .25s ease, box-shadow .25s ease;
    z-index: 60; font-size: 1.05rem; box-shadow: var(--shadow);
}
.coverflow-chevron:hover:not(:disabled) { border-color: var(--gold); color: var(--gold-dark); }
.coverflow-chevron:active:not(:disabled) { transform: translateY(-50%) scale(.92); }
.coverflow-chevron:disabled { opacity: .3; cursor: not-allowed; }
.coverflow-chevron-prev { left: 4px; }
.coverflow-chevron-next { right: 4px; }
@media (max-width: 575.98px) {
    .coverflow-chevron { width: 36px; height: 36px; font-size: .9rem; }
}

/* Lightbox caption overlay (title/category/description) - GLightbox
   normally renders each slide as .ginner-container > .gslide-media
   (image/video) and .gslide-description (built from GALLERY_DATA's
   title/description in index.php) as two stacked siblings, with the
   caption in its own box below the media. To make the caption sit ON TOP
   of the image/video instead, both elements are placed in the same CSS
   grid cell (grid-area: 1/1 - the same stacking trick the testimonial
   slider above uses) so the caption hugs the bottom edge of whatever the
   media's own rendered size turns out to be, rather than pushing it up to
   make room underneath. Scoped to .desc-bottom, the only descPosition this
   site ever sets (see index.php/main.js), so it only touches photo/video
   slides and leaves any other GLightbox slide type untouched. */
.ginner-container.desc-bottom {
    display: grid;
    grid-template-columns: auto;
    grid-template-rows: auto;
    justify-content: center;
    align-content: center;
    height: 100vh;
}
.ginner-container.desc-bottom .gslide-media {
    grid-area: 1 / 1;
    align-self: center;
    justify-self: center;
    margin: 0;
}
.ginner-container.desc-bottom .gslide-description {
    grid-area: 1 / 1;
    align-self: end;
    justify-self: stretch;
    width: 100%;
    max-width: 100%;
    max-height: 55%;
    overflow-y: auto;
    margin: 0;
    box-sizing: border-box;
    /* Empty gradient padding shouldn't block clicking/zooming the image or
       reaching native video controls underneath - only real links inside
       the caption (GLightbox's own "See more" toggle) opt back in below. */
    pointer-events: none;
    background: linear-gradient(to top, rgba(0,0,0,.88) 0%, rgba(0,0,0,.62) 55%, rgba(0,0,0,0) 100%);
    padding: 48px 28px 20px;
    text-align: start;
    z-index: 2;
}
.ginner-container.desc-bottom .gslide-desc a { pointer-events: auto; }
/* Caption text starts faded out and shifted down; main.js adds
   .gdesc-revealed to .gslide-description once that slide's own image/video
   has actually finished loading (GLightbox's afterSlideLoad callback), so
   the fade-up plays exactly when there's something loaded to reveal it
   over instead of racing the media itself. */
.ginner-container.desc-bottom .gdesc-inner {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity .6s ease, transform .6s ease;
}
.ginner-container.desc-bottom .gslide-description.gdesc-revealed .gdesc-inner {
    opacity: 1;
    transform: translateY(0);
}
/* GLightbox ships its own "mobile caption" stylesheet block
   (.glightbox-mobile .gslide-description), added when it detects a touch/
   small-screen device, that sets position:absolute; bottom:0. That matches
   our own .gslide-description rule's specificity exactly, and since
   position/bottom aren't properties we otherwise set, GLightbox's own
   values win by default - which pulls the caption out of the grid cell
   entirely and pins it to the bottom of the whole slide (100vh) instead of
   the bottom of the image, undoing the "hug the image" alignment on
   exactly the devices this overlay matters most for. One extra class of
   specificity (still no !important needed) puts it back as a normal grid
   item, so align-self/grid-area above apply the same way they do on desktop. */
.glightbox-mobile .ginner-container.desc-bottom .gslide-description {
    position: static;
}
.gslide-title {
    color: #fff;
    font-family: var(--font-heading);
    font-size: 1.25rem;
    font-weight: 600;
    margin: 0 0 6px;
    line-height: 1.3;
}
.gslide-meta {
    color: var(--gold);
    font-weight: 600;
    font-size: .8rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    margin-bottom: 4px;
}
.gslide-summary { color: #d8d8d8; font-size: .92rem; margin: 0; }
@media (max-width: 575.98px) {
    .ginner-container.desc-bottom .gslide-description { padding: 32px 16px 14px; }
    .gslide-title { font-size: 1.05rem; }
}

/* ---------------- Packages ---------------- */
.package-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 32px; height: 100%; position: relative; transition: transform var(--transition), box-shadow var(--transition);
}
.package-card:hover { transform: translateY(-6px); box-shadow: var(--shadow); }
.package-price { font-family: var(--font-heading); font-size: 2.4rem; color: var(--gold-dark); font-weight: 700; }
.package-card ul { list-style: none; padding: 0; margin: 20px 0; }
.package-card ul li { padding: 6px 0; color: var(--text-muted); }
.package-card ul li i { color: var(--gold-dark); margin-inline-end: 8px; }

/* ---------------- Testimonials (slider) ----------------
   One slide fills the viewport at a time; every slide sits stacked in the
   same CSS grid cell (grid-area: 1/1) so the track's height auto-fits the
   tallest testimonial with no JS measuring, and main.js moves each slide
   independently via its own translateX(offset*100%) - offset is wrapped
   to the shortest signed distance around the loop (see tsRender() in
   main.js), which is what makes the "infinite" loop actually seamless in
   either direction instead of needing cloned slides at each end. */
.testimonial-slider-wrap { position: relative; max-width: 720px; margin: 0 auto; }
.testimonial-viewport { overflow: hidden; }
.testimonial-track {
    display: grid; grid-template-columns: 100%;
    /* touch-action:pan-y leaves vertical page scroll alone (so a swipe
       that's mostly vertical still scrolls the page on mobile) while
       letting main.js's pointer handlers own horizontal drags; cursor
       communicates draggability on desktop the same way the gallery does. */
    cursor: grab; touch-action: pan-y; user-select: none;
}
.testimonial-track.dragging { cursor: grabbing; }
.testimonial-slide {
    grid-area: 1 / 1; width: 100%; box-sizing: border-box;
    padding: 4px 62px; will-change: transform;
}
.testimonial-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 32px; height: 100%; text-align: center;
}
.testimonial-stars { color: var(--gold); margin-bottom: 12px; }
.testimonial-name { font-weight: 600; margin-top: 16px; }
.testimonial-slider-wrap.no-nav .testimonial-slide { padding: 4px 8px; }
@media (max-width: 575.98px) {
    .testimonial-slide { padding: 4px 46px; }
    .testimonial-card { padding: 24px 20px; }
    .testimonial-slider-wrap.no-nav .testimonial-slide { padding: 4px 8px; }
}

/* ---------------- Contact / Request Form ---------------- */
.request-form-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 40px; box-shadow: var(--shadow);
}
.form-control, .form-select {
    background: var(--bg-alt); border: 1px solid var(--border); color: var(--text);
    padding: 12px 16px; border-radius: 10px;
}
.form-control:focus, .form-select:focus {
    background: var(--bg-alt); color: var(--text); border-color: var(--gold);
    box-shadow: 0 0 0 .2rem rgba(34,141,255,.2);
}
.form-label { font-weight: 500; margin-bottom: 6px; }
::placeholder { color: var(--text-muted); opacity: .7; }

/* ---------------- Footer ---------------- */
footer { background: var(--bg-alt); border-top: 1px solid var(--border); padding: 50px 0 24px; }
.social-links a { display: inline-flex; width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--border); align-items: center; justify-content: center; margin-inline-end: 8px; color: var(--text); }
.social-links a:hover { border-color: var(--gold); color: var(--gold-dark); }
.footer-link { color: inherit; text-decoration: none; }
.footer-link:hover { color: var(--gold-dark); text-decoration: underline; }

/* ---------------- Status timeline (tracking page) ---------------- */
.timeline { position: relative; padding-inline-start: 30px; }
.timeline::before { content:''; position:absolute; inset-inline-start: 9px; top:0; bottom:0; width:2px; background: var(--border); }
.timeline-step { position: relative; padding-bottom: 28px; }
.timeline-step::before {
    content:''; position:absolute; inset-inline-start:-30px; top:2px; width:20px; height:20px; border-radius:50%;
    background: var(--surface); border: 3px solid var(--border);
}
.timeline-step.done::before { border-color: var(--gold); background: var(--gold); }
.timeline-step .step-title { font-weight: 600; }
.timeline-step .step-time { color: var(--text-muted); font-size: .85rem; }

/* Star rating widget */
.star-rating { display: inline-flex; flex-direction: row-reverse; font-size: 2rem; gap: 4px; }
.star-rating input { display: none; }
.star-rating label { color: var(--border); cursor: pointer; transition: color .2s; }
.star-rating input:checked ~ label,
.star-rating label:hover,
.star-rating label:hover ~ label { color: var(--gold); }

/* Utility */
.badge-status { padding: 6px 14px; border-radius: 999px; font-size: .78rem; font-weight: 600; }
.card-muted { background: var(--bg-alt); border: 1px dashed var(--border); border-radius: 12px; padding: 20px; }

[dir="rtl"] .filter-bar, [dir="rtl"] .text-center-block { direction: rtl; }
