/* ═══════════════ SATR — سطر ═══════════════ */

/* ---------- Fonts ---------- */
/* Vazirmatn, self-hosted like everything else here. ONE file per script,
   variable across 100→900, so every weight on the site costs two requests
   in total — the alternative (a static face per weight) was five files per
   script for a page that uses four of them. */
@font-face{
  font-family:'Vazirmatn';
  src:url('fonts/vazirmatn-arabic.woff2') format('woff2');
  font-weight:100 900;font-display:swap;
  unicode-range:U+0600-06FF,U+0750-077F,U+0870-088E,U+0890-0891,U+0897-08E1,
                U+08E3-08FF,U+200C-200E,U+2010-2011,U+204F,U+2E41,U+FB50-FDFF,
                U+FE70-FE74,U+FE76-FEFC;
}
@font-face{
  font-family:'Vazirmatn';
  src:url('fonts/vazirmatn-latin.woff2') format('woff2');
  font-weight:100 900;font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,
                U+02DC,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,
                U+FEFF,U+FFFD;
}

/* ---------- Tokens ---------- */
/* The four brand values and nothing else. Never hardcode one of them —
   the paper and the red in particular are each used in a dozen places. */
:root{
  --paper:#DDDDDD;      /* background */
  --ink:#222831;        /* accent 1 — type, the dark band */
  --slate:#30475E;      /* accent 2 */
  /* The band behind the film clip. It is COLOUR-PICKED OUT OF THE ENCODED
     FILE, never assumed from the hex the artwork was painted in — the clip is
     a full-width flat rectangle sitting on this colour, so a mismatch of even
     a unit or two shows up as a seam along its top and bottom edges.

     It is one unit off --slate, and that unit is the 8-bit YUV 4:2:0 round
     trip, which nothing can remove. It used to be four units off in all three
     channels, because H.264 defaults to limited range and squeezes 0–255 into
     16–235: tools/build-assets.py now encodes full range, which is also what
     brings the mark's own grey back to exactly --paper.

     tools/build-assets.py prints this value every time it re-encodes the clip.
     If the master changes, paste what it prints. */
  --band:#30475C;
  --red:#F05454;        /* primary — the dot, every call to action */

  --card:#FFFFFF;       /* a pasted card on the paper */
  --ink-2:rgba(34,40,49,.66);
  --ink-3:rgba(34,40,49,.42);
  --line:rgba(34,40,49,.14);
  --on-dark:#DDDDDD;
  --on-dark-2:rgba(221,221,221,.62);

  --radius:14px;
  --spring:cubic-bezier(.34,1.45,.55,1);
  --ease:cubic-bezier(.22,.8,.28,1);
  --sec-pad:clamp(64px,7vw,116px);
  --nav-h:80px;

  /* The master artwork's own aspect. Written once because the hero's cover
     arithmetic and the camera's placement both depend on it. */
  --art-ratio:1.77778;
}

/* ---------- Base ---------- */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:auto;}
body{
  font-family:'Vazirmatn','Segoe UI',Tahoma,sans-serif;
  background:var(--paper);
  color:var(--ink);
  line-height:1.8;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  font-variant-ligatures:common-ligatures contextual;
  font-feature-settings:"liga" 1,"clig" 1,"calt" 1,"rlig" 1;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
.container{width:min(1240px,92vw);margin-inline:auto;}
::selection{background:rgba(240,84,84,.28);}
a:focus-visible,button:focus-visible{outline:2px solid var(--red);outline-offset:3px;border-radius:6px;}

/* THE brand mark, and it is a shape rather than a picture: the red dot that
   opens the logo is set before every header on the site, and at these sizes
   an <img> would be a request for something CSS already draws exactly. */
.dot{
  display:inline-block;width:.62em;height:.62em;border-radius:50%;
  background:var(--red);flex:none;
  vertical-align:.02em;
}

/* Latin runs are set in caps with real tracking — the mockup's nav, the
   eyebrows, the scroll cue. Arabic never gets letter-spacing: it would break
   the joins. */
.caps{
  font-family:'Vazirmatn',sans-serif;
  text-transform:uppercase;letter-spacing:.22em;font-weight:500;
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.55em;
  font-family:inherit;font-size:1rem;font-weight:700;line-height:1;
  padding:1.05em 2.2em;border-radius:100px;border:0;cursor:pointer;
  position:relative;overflow:hidden;white-space:nowrap;
  transition:transform .45s var(--spring),box-shadow .45s var(--ease),
             background-color .3s,color .3s;
  will-change:transform;
}
.btn svg{width:1.05em;height:1.05em;flex:none;}
.btn:active{transform:scale(.96);}
.btn-ink{background:var(--ink);color:var(--paper);}
.btn-ink:hover{transform:translateY(-3px);box-shadow:0 16px 30px -14px rgba(34,40,49,.6);}
.btn-red{background:var(--red);color:#fff;}
.btn-red:hover{transform:translateY(-3px);box-shadow:0 16px 32px -14px rgba(240,84,84,.65);}
.btn-paper{background:rgba(255,255,255,.72);color:var(--ink);box-shadow:inset 0 0 0 1.5px var(--line);}
.btn-paper:hover{transform:translateY(-3px);background:#fff;box-shadow:inset 0 0 0 1.5px var(--ink),0 14px 28px -16px rgba(34,40,49,.45);}

/* the sheen sweep, shared by both filled buttons */
.btn-ink::before,.btn-red::before{
  content:"";position:absolute;top:0;bottom:0;width:44%;
  inset-inline-start:-60%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.28),transparent);
  transform:skewX(-18deg);transition:inset-inline-start .7s var(--ease);
}
.btn-ink:hover::before,.btn-red:hover::before{inset-inline-start:120%;}

/* ---------- Intro splash ---------- */
/* .exit is added by JS once the tracked assets have settled and the 1s
   minimum has elapsed, rather than on a fixed delay — the wait is dynamic. */
.intro{
  position:fixed;inset:0;z-index:10000;
  background:var(--paper);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:26px;
}
.intro.exit{animation:introExit .65s var(--ease) forwards;}
.intro-logo{
  width:min(240px,52vw);height:auto;
  opacity:0;transform:translateY(28px);
  animation:introLogoIn .6s var(--ease) .1s forwards;
}
.intro.exit .intro-logo{
  animation:introLogoIn .6s var(--ease) .1s forwards,
            introLogoOut .35s var(--ease) forwards;
}
@keyframes introLogoIn{to{opacity:1;transform:translateY(0);}}
@keyframes introLogoOut{to{opacity:0;}}

/* The fill's scaleX is written from JS as each tracked asset settles.
   transform, not width, so every step is composited rather than re-laid out,
   and the transition smooths the discrete jumps into one sweep. */
.intro-bar{
  position:relative;width:min(210px,44vw);height:3px;border-radius:3px;
  background:rgba(34,40,49,.14);overflow:hidden;
  opacity:0;animation:introBarIn .4s var(--ease) .45s forwards;
}
.intro-bar i{
  position:absolute;inset:0;border-radius:inherit;background:var(--red);
  transform:scaleX(0);transform-origin:right;   /* RTL: fills right → left */
  transition:transform .45s var(--ease);
}
@keyframes introBarIn{to{opacity:1;}}
.intro.exit .intro-bar{animation:introBarIn .4s var(--ease) .45s forwards,introBarOut .25s ease forwards;}
@keyframes introBarOut{to{opacity:0;}}
@keyframes introExit{to{transform:translateY(-100%);}}

/* the hero's entrance stays paused until the splash lifts, so it plays fresh
   on reveal instead of finishing off-screen behind the panel */
body.pre-reveal .hero-logo-slot,
body.pre-reveal .logo-fly img,
body.pre-reveal .hero-lede,
body.pre-reveal .hero-actions,
body.pre-reveal .hero-scroll,
body.pre-reveal .hero-art-box{animation-play-state:paused;}

/* ---------- Nav ---------- */
.nav{
  position:fixed;top:0;inset-inline:0;z-index:100;
  transition:background-color .4s var(--ease),box-shadow .4s,backdrop-filter .4s;
}
/* The header is PRESENT across the hero and simply has no bar and no mark:
   the links sit straight on the paper, and both the background and the logo
   arrive together as the page scrolls past.

   It is never hidden or moved by a transform. script.js measures
   .nav-brand img to know where #logoFly has to land, and a header parked
   off-screen — or one that is display:none — hands it a docking box that is
   off-screen too. That is also why the brand keeps `visibility:hidden` rather
   than being removed: it is the measuring stick, and it holds the gap the
   mark is about to fly into. .nav-flight is added by script.js, so with JS
   off the logo is simply always there. */
body.nav-flight .nav-brand img{visibility:hidden;}
.nav.nav-on{
  background:rgba(221,221,221,.82);
  -webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);
  box-shadow:0 1px 0 var(--line);
}
.nav-inner{
  width:min(1320px,94vw);margin-inline:auto;
  display:flex;align-items:center;gap:2.4rem;
  height:var(--nav-h);
}
.nav-brand img{height:36px;width:auto;transition:transform .5s var(--spring);}
.nav-brand:hover img{transform:translateY(-2px);}
.nav-links{
  display:flex;gap:2.1rem;margin-inline-start:auto;
  font-size:.78rem;font-weight:600;
  text-transform:uppercase;letter-spacing:.2em;
}
.nav-links a{color:var(--ink-2);position:relative;padding:.4em 0;transition:color .3s;}
.nav-links a::after{
  content:"";position:absolute;bottom:0;inset-inline-start:0;height:2px;width:100%;
  background:var(--red);border-radius:2px;
  transform:scaleX(0);transform-origin:center;transition:transform .4s var(--spring);
}
.nav-links a:hover{color:var(--ink);}
.nav-links a:hover::after,.nav-links a.active::after{transform:scaleX(1);}
.nav-links a.active{color:var(--ink);}
.nav-cta{font-size:.86rem;padding:.85em 1.7em;}
.nav-burger{
  display:none;background:none;border:0;cursor:pointer;
  width:44px;height:44px;flex-direction:column;justify-content:center;align-items:center;gap:5px;
}
.nav-burger span{display:block;width:22px;height:2px;background:var(--ink);border-radius:2px;transition:transform .35s var(--ease),opacity .3s;}
.nav-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg);}
.nav-burger[aria-expanded="true"] span:nth-child(2){opacity:0;}
.nav-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg);}
.nav-drawer{
  display:none;flex-direction:column;gap:1.15rem;padding:1.4rem 6vw 2rem;
  background:rgba(221,221,221,.97);
  -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);
  border-bottom:1px solid var(--line);
}
.nav-drawer a{font-weight:600;font-size:.95rem;letter-spacing:.16em;}
.nav-drawer .btn{align-self:flex-start;letter-spacing:0;}
.nav-drawer.open{display:flex;}

/* ---------- Hero ---------- */
/* Paper, one collage plate, and the copy over the empty half of it. */
.hero{
  position:relative;min-height:100svh;
  display:flex;align-items:center;
  padding-block:calc(var(--nav-h) + 20px) 40px;
  padding-inline:max(5vw,calc((100vw - 1240px) / 2));
  overflow:hidden;
  background:var(--paper);
  /* NO `isolation:isolate` and no z-index here, deliberately. Both would make
     .hero a stacking context, and .hero-grain has to out-paint two elements
     that live OUTSIDE it — #logoFly (101) and .nav (100). Nothing in this
     section uses a negative z-index, which is the only thing the isolation
     was ever guarding against. */
}

/* THE COLLAGE. One box at the master's own 16:9 holds both pictures, so the
   camera cannot drift off the dark blob painted into the plate behind it:
   its left/top/width below are the exact fractions its alpha bounding box
   occupies in the 1920×1080 master.

   The box is anchored to the PHYSICAL left and bottom, not to the inline
   start. This is a coordinate inside a picture, not a reading order — the
   torn paper edge and the camera are both drawn against the artwork's left,
   and anchoring there is what guarantees neither is ever the thing that gets
   cropped. What overflows instead is the empty wash on the other side, which
   is exactly where the copy sits.

   `width:max(100%, 177.78svh)` is cover arithmetic done in CSS rather than by
   `background-size`, because a background can only cover one image and this
   is two that have to crop identically. */
.hero-art{position:absolute;inset:0;overflow:hidden;z-index:0;}
.hero-art-box{
  position:absolute;left:0;bottom:0;
  width:max(100%,calc(100svh * var(--art-ratio)));
  aspect-ratio:var(--art-ratio);
}
.hero-plate{position:absolute;inset:0;width:100%;height:100%;object-fit:fill;}
.hero-cam{
  position:absolute;
  left:6.5104%;top:17.6852%;width:43.8542%;height:auto;
}

/* Paper grain, screen-blended so it only ever adds the light specks. It is
   the TOP layer of the hero — over the collage, over the logo, over the copy
   and over the buttons — because the whole point of a collage is that one
   sheet of paper was photographed, not that six clean elements were stacked
   on a textured backdrop. `pointer-events:none` is what keeps a full-bleed
   layer at z-index 5 from swallowing every click under it, and the two links
   below it stay perfectly clickable because of it.

   `screen` never darkens, so the ink type underneath only ever picks up the
   texture's light specks; it cannot be dimmed by this.

   102 beats #logoFly's 101 and the header's 100, so the flying mark and the
   nav links are grained too while they are over the hero — which is the whole
   claim the layer makes. It is clipped to .hero, so it stops existing the
   moment the hero has scrolled away and the header carries its own bar.

   It may live here and NOWHERE near .band — a full-bleed layer over a <video>
   denies the clip the compositor's overlay path, so every decoded frame is
   re-composited through whatever is on top and a frame dropped there reads as
   the picture popping. The hero has no video in it, which is the only reason
   this is allowed to be full-bleed at all. */
.hero-grain{
  position:absolute;inset:0;z-index:102;pointer-events:none;
  background:url("assets/texture.webp") center/cover no-repeat;
  mix-blend-mode:screen;opacity:.5;
}

.hero-copy{
  position:relative;z-index:2;
  width:min(52ch,46%);
  display:flex;flex-direction:column;align-items:flex-start;
  gap:clamp(20px,2.2vw,30px);
}
.hero-logo-slot{display:block;width:min(430px,100%);}
.hero-logo{width:100%;height:auto;}
.hero-lede{
  font-size:clamp(1rem,1.12vw,1.15rem);
  font-weight:400;color:var(--ink-2);max-width:44ch;
  line-height:1.95;
}
.hero-actions{display:flex;flex-wrap:wrap;gap:14px;}

.hero-scroll{
  position:absolute;z-index:2;
  bottom:32px;inset-inline-start:max(5vw,calc((100vw - 1240px) / 2));
  display:flex;align-items:center;gap:12px;
  font-size:.68rem;font-weight:600;text-transform:uppercase;letter-spacing:.28em;
  color:var(--ink-3);transition:color .3s;
}
.hero-scroll:hover{color:var(--red);}
.hero-scroll-line{
  display:block;width:54px;height:1.5px;background:currentColor;
  transform-origin:right;animation:scrollPulse 2.4s var(--ease) infinite;
}
@keyframes scrollPulse{0%,100%{transform:scaleX(.4);}50%{transform:scaleX(1);}}

/* --- entrance choreography --- */
/* Every item resolves out of a small blur and a lift. The animation is taken
   back off once it has played (body.hero-settled, added from script.js when
   the last one ends) — `forwards` does not mean finished, it means the last
   keyframe keeps applying, and a filter is a live render surface at any
   radius, blur(0) included. Six of them stacked over the collage is six
   surfaces the compositor has to keep for the rest of the visit.

   It is counted off `animationend` rather than run on a timer because
   body.pre-reveal pauses the whole set until the splash lifts, so wall-clock
   from load says nothing about where the animation has actually got to. */
@keyframes satrIn{
  from{opacity:0;transform:translateY(26px);filter:blur(10px);}
  to{opacity:1;transform:none;filter:blur(0);}
}
@keyframes artIn{
  from{opacity:0;transform:translateX(-26px) scale(1.03);}
  to{opacity:1;transform:none;}
}
.hero-art-box{opacity:0;animation:artIn 1.15s var(--ease) .1s forwards;}
.hero-logo-slot,.logo-fly img,.hero-lede,.hero-actions,.hero-scroll{
  opacity:0;animation:satrIn .85s var(--ease) forwards;
}
.hero-logo-slot,.logo-fly img{animation-delay:.30s;}
.hero-lede{animation-delay:.46s;}
.hero-actions{animation-delay:.60s;}
.hero-scroll{animation-delay:.78s;}

body.hero-settled .hero-logo-slot,
body.hero-settled .logo-fly img,
body.hero-settled .hero-lede,
body.hero-settled .hero-actions,
body.hero-settled .hero-scroll,
body.hero-settled .hero-art-box{
  /* kills the animation as well as setting the properties: a fill outranks a
     normal declaration, so `filter:none` alone would lose to it */
  animation:none;opacity:1;transform:none;filter:none;
}

/* --- the mark's flight into the navbar --- */
/* Its box is set from .nav-brand img's rect — the DESTINATION — so docked is
   transform:none and every rounding error lands at the START of the journey,
   over a hero that is scrolling past, rather than sitting permanently half a
   pixel off in the navbar. */
.logo-fly{
  position:fixed;z-index:101;pointer-events:none;
  transform-origin:top right;   /* physical: the mark's own corner in an RTL
                                   page, and the corner .nav-brand grows from */
}
.logo-fly img{width:100%;height:auto;}
body.nav-flight .hero-logo-slot{visibility:hidden;}

/* ---------- Sections shared ---------- */
.section{padding-block:var(--sec-pad);position:relative;}
.sec-head{margin-bottom:clamp(34px,4vw,54px);max-width:60ch;}
.eyebrow{
  font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.28em;
  color:var(--ink-3);margin-bottom:.9rem;
}
/* The dot opens every header on the site — it is the logo's own first mark. */
.sec-title{
  font-size:clamp(2.1rem,5.2vw,3.6rem);font-weight:900;line-height:1.35;
  display:flex;align-items:center;gap:.42em;
}
.sec-title::before{
  content:"";width:.34em;height:.34em;border-radius:50%;
  background:var(--red);flex:none;
}
.sec-sub{margin-top:.7rem;color:var(--ink-2);font-size:1.02rem;max-width:52ch;}

/* scroll reveal — one class, added by an IntersectionObserver */
.reveal{opacity:0;transform:translateY(26px);transition:opacity .8s var(--ease),transform .8s var(--ease);}
.reveal.in{opacity:1;transform:none;}

/* ---------- Keyword strip ---------- */
/* The hero's handover. One ink rule above it and one below, the words
   travelling slowly between them, separated by the brand dot. The track is
   moved by a transform inside a rAF loop that only runs while the strip is on
   screen — see script.js; a marquee animating a long cloned track for the
   whole time a visitor sits on the hero is continuous compositor work with
   nothing to show for it. */
.strip{
  overflow:hidden;
  border-block:1px solid var(--line);
  padding-block:14px;
  background:var(--paper);
}
.strip-track{display:flex;width:max-content;will-change:transform;}
.strip-set{display:flex;align-items:center;flex:none;}
.strip-set span{
  font-size:clamp(.95rem,1.6vw,1.25rem);font-weight:600;
  padding-inline:clamp(14px,1.6vw,22px);white-space:nowrap;
  color:var(--ink);
}
.strip-set i{width:8px;height:8px;border-radius:50%;background:var(--red);flex:none;}

/* ---------- Projects ---------- */
/* Centred flex, not an auto-fit grid: a grid parks the last row's orphan
   against the start edge, and these are meant to read as pasted cards. */
.work-grid{display:flex;flex-wrap:wrap;gap:clamp(16px,1.8vw,24px);justify-content:center;}
.vcard{
  flex:0 1 348px;max-width:100%;
  background:var(--card);border-radius:var(--radius);overflow:hidden;
  box-shadow:0 1px 0 var(--line),0 18px 34px -28px rgba(34,40,49,.5);
  transition:transform .5s var(--spring),box-shadow .5s var(--ease);
  position:relative;
}
.vcard:hover{transform:translateY(-6px);box-shadow:0 1px 0 var(--line),0 26px 44px -26px rgba(34,40,49,.6);}
/* `display:block` is load-bearing, not tidiness: these are <span>s (the card
   is an <a>, so it may not contain a <div>), and `aspect-ratio` does not apply
   to an inline box at all. Without it the media box takes its height from the
   image's own intrinsic ratio and the tiles come out square. */
.vcard-media{display:block;position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--ink);}
.vcard-img{width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease);}
.vcard:hover .vcard-img{transform:scale(1.05);}
/* The scrim the play badge sits on. Ink, not the old brand gradient — a
   coloured wash over seven different thumbnails tints seven different
   pictures, and none of them were graded for it. */
.vcard-media::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(to top,rgba(34,40,49,.55),transparent 52%);
  opacity:0;transition:opacity .45s var(--ease);
}
.vcard:hover .vcard-media::after{opacity:1;}
.vcard-play{
  position:absolute;inset-block-end:12px;inset-inline-end:12px;
  width:44px;height:44px;border-radius:50%;
  display:grid;place-items:center;
  background:var(--red);color:#fff;
  transform:scale(.7);opacity:0;
  transition:transform .45s var(--spring),opacity .35s var(--ease);
}
.vcard-play svg{width:17px;height:17px;}
.vcard:hover .vcard-play{transform:none;opacity:1;}
.vcard-cap{display:block;padding:16px 18px 20px;}
.vcard-title{display:block;}
.vcard-tag{
  display:inline-flex;align-items:center;gap:.5em;
  font-size:.66rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--red);margin-bottom:.5rem;
}
.vcard-tag::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;}
.vcard-title{font-size:.98rem;font-weight:600;line-height:1.6;color:var(--ink);}

/* ---------- The film band ---------- */
/* The one dark block in the page, and the clip is composited onto exactly
   this colour at encode time — change one and the other has to be re-encoded
   (see the keyer note in CLAUDE.md). Full-bleed via 100vw rather than a
   wrapper, so the paper either side of the container never shows through. */
/* Slate, and the footer below it is ink — two dark tones in that order, so
   the page steps down rather than running one long dark block into another. */
.band{
  background:var(--band);color:var(--on-dark);
  margin-inline:calc(50% - 50vw);width:100vw;
  padding-block:clamp(56px,6vw,92px);
  overflow:hidden;   /* the clip's own min-width overhangs on a phone */
  position:relative; /* the wash below is absolute against this */
}
/* The wash: the clip again, scaled up so that only its empty top-left corner
   is on screen. 800% of the band's width puts the artwork — which starts a
   third of the way across the frame — far outside the box, so what covers the
   section is a flat field of the clip's own colour, arriving through the same
   decoder as the clip itself. See the note in index.html for why this beats
   naming the colour in CSS.

   --band stays underneath as the fallback: a browser that renders no frame
   here shows the stylesheet's best guess rather than a hole. */
.band-wash{
  position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none;
  background:var(--band);
}
.band-wash video{
  position:absolute;top:0;left:0;
  width:800%;height:auto;
  pointer-events:none;
}
.band-inner{
  position:relative;z-index:1;   /* over the wash */
  display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:clamp(18px,2.4vw,30px);
  width:100%;
}
/* NOTHING is laid over this element — no grain, no backdrop-filter, no CSS
   filter. Each of those takes the clip off the compositor's video path and
   re-composites every decoded frame through the layer on top, which reads as
   the picture dropping out for a frame. */
/* Full-bleed. The master's whole width is kept — only the top and bottom are
   cropped — because the line sweeps in from the right edge of the frame and
   cropping the sides clips that entrance mid-stroke.

   `min-width` is the one concession: at 5.8:1, a viewport-width clip on a
   phone is a 65px ribbon with an illegible mark in it. Below ~720px the clip
   stops shrinking and the band's own overflow takes the difference off both
   ends — which costs nothing visible, because the clip's background and the
   band are the same colour, and the only thing out past the edges is empty
   plate and the tail of the sweep. */
.band-clip{
  display:block;width:100%;min-width:720px;height:auto;
  background:var(--band);
  /* THE EDGES ARE FEATHERED, AND THAT IS THE ACTUAL FIX FOR THE SEAM.

     Matching the band's hex to the clip's cannot be made to work across
     browsers. script.js samples the painted pixel and writes it into --band,
     which makes Chrome exact — but Safari and Firefox colour-manage a canvas
     readback and the compositor differently, so the number that is right in
     one is wrong in the others, and there is no single value that satisfies
     all three. Three earlier attempts are in CLAUDE.md; none of them close it.

     So stop trying to land on a number and remove the boundary instead. The
     clip dissolves into the band over its own empty margins, and a couple of
     units of difference spread across a 30px gradient is not something an eye
     can find. The stops are inside those margins on purpose — the artwork
     occupies 13.6%–89.4% of the frame's height, so 10% and 92% never touch
     it. Re-crop the clip and these two numbers move with it.

     Only the top and bottom are feathered: the clip is full-bleed, so its
     left and right edges are the viewport's and have no band beside them. */
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 10%,#000 92%,transparent 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 10%,#000 92%,transparent 100%);
}
/* `text-wrap:balance` so the two lines come out even. Without it this broke
   after «السطر» and left «كامل.» sitting alone on the second row, which reads
   as a mistake rather than as a line. The max-width is what forces two lines
   in the first place; balance only decides where they fall. Engines without
   it get the old behaviour, which is why the wording is also short enough
   that the orphan is one word rather than half a clause. */
.band-line{
  font-size:clamp(1.05rem,1.7vw,1.35rem);font-weight:500;
  color:var(--on-dark);max-width:38ch;
  text-wrap:balance;
}
.band-cta{margin-top:.2rem;}

/* Five names on one line, separated by the brand dot. No plate under any of
   them — the client asked for text on the band, not for five chips. */
.band-specs{
  list-style:none;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:.9rem clamp(14px,2vw,26px);
  margin-top:clamp(10px,1.6vw,20px);
  font-size:clamp(.82rem,1.05vw,.95rem);font-weight:500;
  color:var(--on-dark-2);
}
.band-specs li{display:flex;align-items:center;gap:clamp(14px,2vw,26px);}
.band-specs li:not(:last-child)::after{
  content:"";width:7px;height:7px;border-radius:50%;background:var(--red);flex:none;
}

/* ---------- Footer ---------- */
.footer{
  background:var(--ink);color:var(--on-dark);
  padding-block:clamp(52px,5.5vw,80px) 0;
}
.footer-top{
  display:grid;gap:clamp(28px,3.2vw,48px);
  grid-template-columns:1.5fr 1fr 1.2fr 1.2fr;
  padding-bottom:clamp(34px,4vw,54px);
}
.footer-logo{width:min(210px,60%);height:auto;margin-bottom:1.1rem;}
.footer-desc{color:var(--on-dark-2);font-size:.94rem;max-width:38ch;}
.footer-col h4{
  display:flex;align-items:center;gap:.55em;
  font-size:.95rem;font-weight:700;margin-bottom:1.05rem;
}
.footer-col a,.footer-col span{
  display:block;color:var(--on-dark-2);font-size:.92rem;
  padding-block:.3rem;transition:color .3s,transform .3s var(--ease);
}
.footer-col a:hover{color:#fff;transform:translateX(-3px);}
.footer-social{display:flex;gap:10px;margin-bottom:1.1rem;}
.footer-social a{
  width:38px;height:38px;border-radius:50%;display:grid;place-items:center;
  background:rgba(255,255,255,.09);color:var(--on-dark);
  transition:background-color .3s,color .3s,transform .35s var(--spring);
}
.footer-social a:hover{background:var(--red);color:#fff;transform:translateY(-3px);}
.footer-social svg{width:17px;height:17px;}
.footer-inq{font-size:.85rem;color:var(--on-dark-2);margin-bottom:1rem;line-height:1.75;}
.footer-inq a{display:inline;padding:0;}
.footer-cta{font-size:.88rem;padding:.9em 1.6em;}
.footer-bar{
  display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between;align-items:center;
  padding-block:20px;border-top:1px solid rgba(255,255,255,.12);
  font-size:.8rem;color:var(--on-dark-2);
}
.footer-made{display:flex;align-items:center;gap:.6em;letter-spacing:.18em;font-size:.7rem;font-weight:600;}

/* ---------- Responsive ---------- */
@media (max-width:1020px){
  .nav-links{display:none;}
  .nav-burger{display:flex;}
  .nav-cta{display:none;}
  .nav-inner{gap:1rem;justify-content:space-between;}

  /* The burger arrives with the finished navbar, not before it. Across the
     hero the header has no bar and no mark, and a lone hamburger floating on
     the artwork belongs to nothing; `.nav-docked` is written by script.js at
     the point the mark has actually settled into place, which is later than
     `.nav-on` (the bar's own arrival).

     Scoped to body.nav-flight, so with JS off — or under reduced motion,
     where the flight never runs — the burger is simply always there, which is
     the only state those visitors will ever see.

     `visibility` is transitioned with a 0s delay on the way in and a delayed
     one on the way out, which is what actually takes it out of the tab order
     while it is invisible; opacity alone would leave a focusable control
     sitting over the hero. */
  body.nav-flight .nav-burger{
    opacity:0;visibility:hidden;transform:translateY(-6px);
    transition:opacity .4s var(--ease),transform .4s var(--ease),visibility 0s linear .4s;
  }
  body.nav-flight .nav.nav-docked .nav-burger{
    opacity:1;visibility:visible;transform:none;
    transition:opacity .5s var(--ease),transform .5s var(--spring),visibility 0s;
  }

  /* The camera goes UNDER the copy rather than beside it. The artwork's own
     wash is exactly --paper, so the plate can simply end mid-screen with no
     seam and no mask needed — the box keeps its bottom-left anchor.

     Both the box width and its window's height are driven off ONE number, so
     the camera is framed identically at every width. `--w` is the width at
     which the camera comes out about 85vw across (it occupies 43.85% of the
     master), capped so a 1000px tablet does not get a two-metre camera; the
     window is then 0.463·--w tall, which is exactly the part of the artwork
     from the camera's own top edge down. Sizing the window in svh instead is
     the trap: svh and vw scale independently, so the crop slides as the
     window changes shape and the camera loses its head. */
  .hero{
    flex-direction:column;align-items:stretch;justify-content:space-between;
    padding-block:calc(var(--nav-h) + 18px) 0;
    gap:8px;
  }
  /* THE BOX IS SIZED OFF ITS HEIGHT HERE, NOT ITS WIDTH — the reverse of the
     desktop rule above, and the only arrangement that satisfies both halves of
     the phone layout at once.

     Sizing it by width (a --w in vw, capped in px) is the obvious thing and it
     cannot work: the copy above takes whatever height it takes, so a
     width-derived box is either taller than the room left — cropping the
     plate's top, which is the torn edge and the grid — or short enough to fit
     the worst case and pointlessly small everywhere else. Height is the
     constraint, so height is the input. `.hero-art` takes the leftover with
     flex, the box is exactly as tall as that window, and `aspect-ratio` gives
     it its width; the camera follows because it is placed by percentage inside
     the box, so nothing has to be re-tuned and the camera can never drift off
     the blob behind it.

     The cap is there for a tablet in portrait, where "leftover height" is a
     lot of it and the camera would otherwise be enormous. */
  .hero-art{
    position:relative;z-index:0;inset:auto;order:2;
    /* full-bleed out of the hero's own inline padding: the torn paper edge is
       drawn at the artwork's left margin, and a 5vw gutter cuts it off and
       turns the collage into a framed rectangle */
    width:100vw;margin-inline:calc(50% - 50vw);
    flex:1 1 auto;min-height:200px;max-height:min(58svh,440px);
  }
  /* Nudged off the physical left edge. The whole BOX moves, never the camera
     alone — the camera and the blob it sits on are registered by the box, and
     shifting one of them is how they come apart. */
  .hero-art-box{
    height:100%;width:auto;aspect-ratio:var(--art-ratio);
    left:-7%;bottom:0;
  }
  /* The plate's top edge is dissolved rather than cut. Its wash is exactly
     --paper, so the edge was never a colour seam — what gave it away was the
     torn paper and the grid starting abruptly partway down the screen, which
     reads as a picture with a lid on it rather than as paper.

     The mask is on the PLATE only, not on the box: the camera is a separate
     layer inside that box and must stay fully opaque. It ends at 16%, which is
     just above the camera's own top edge at 17.685%, so the cutout is never
     touched however the window is sized. The mic blob begins around 11% and
     loses a little of its crown to this, which is the trade — a hard rule
     across the artwork is worse than a softened blob.

     -webkit- first: Safari still needs the prefix for mask-image. */
  .hero-plate{
    -webkit-mask-image:linear-gradient(to bottom,transparent 0,rgba(0,0,0,.55) 7%,#000 16%);
            mask-image:linear-gradient(to bottom,transparent 0,rgba(0,0,0,.55) 7%,#000 16%);
  }
  .hero-copy{width:100%;order:1;align-items:flex-start;}
  .hero-logo-slot{width:min(330px,74%);}
  .hero-lede{font-size:1rem;}
  .hero-scroll{display:none;}

  .footer-top{grid-template-columns:1fr 1fr;}
  .footer-brand{grid-column:1 / -1;}
}

@media (max-width:600px){
  :root{--nav-h:68px;}
  .hero{padding-block:calc(var(--nav-h) + 10px) 0;}
  .hero-art{height:40svh;min-height:230px;}
  .hero-actions{width:100%;}
  .hero-actions .btn{flex:1 1 auto;justify-content:center;}
  .vcard{flex:1 1 100%;}
  /* Five names do not fit one phone line, and a SEPARATOR that wraps leaves a
     dot dangling at the end of a row with nothing after it. So on a phone the
     dot moves in front of every item and becomes a bullet, which reads
     correctly wherever the line happens to break. */
  .band-specs{gap:.5rem 18px;}
  .band-specs li{gap:.5em;}
  .band-specs li:not(:last-child)::after{display:none;}
  .band-specs li::before{
    content:"";width:6px;height:6px;border-radius:50%;background:var(--red);flex:none;
  }
  .footer-top{grid-template-columns:1fr;}
  .footer-bar{justify-content:center;text-align:center;}
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
  .hero-art-box,.hero-logo-slot,.logo-fly img,.hero-lede,.hero-actions,.hero-scroll{
    opacity:1;transform:none;filter:none;
  }
  .reveal{opacity:1;transform:none;}
  /* the mark never flies, so the header is simply always there and the hero
     keeps painting its own corner mark */
  .logo-fly{display:none;}
}
