/* real Avenir Arabic, served from the site (one Medium weight covers all uses) */
@font-face{
  font-family:"Avenir Arabic";
  src:url("fonts/AvenirArabic-Medium.otf") format("opentype");
  font-weight:300 800;
  font-display:swap;
}
/* Product Sans — big titles + all numbers (Latin only, no Arabic glyphs) */
@font-face{
  font-family:"Product Sans";
  src:url("fonts/ProductSans-Regular.ttf") format("truetype");
  font-weight:400 600;
  font-display:swap;
}
@font-face{
  font-family:"Product Sans";
  src:url("fonts/ProductSans-Bold.ttf") format("truetype");
  font-weight:700 900;
  font-display:swap;
}
/* SF Pro Arabic — used for every Arabic heading */
@font-face{
  font-family:"SF Pro AR";
  src:url("fonts/SFProARDisplay-Semibold.ttf") format("truetype");
  font-weight:400 900;
  font-display:swap;
}

/* ================= TOKENS ================= */
:root{
  /* big titles: Product Sans Bold. For ARABIC titles Product Sans has no
     Arabic glyphs, so it falls through to SF Pro AR (also self-hosted). */
  --font-display:"Product Sans", "SF Pro AR", "Avenir Arabic", sans-serif;
  /* numbers: always Product Sans (Latin digits) */
  --font-num:"Product Sans", "Avenir Arabic", sans-serif;
  /* body / everything else: Avenir Arabic (EN + AR) — all self-hosted */
  --font-body:"Avenir Arabic", "SF Pro AR", sans-serif;

  /* logo inset — matches --logo-x in admin/assets/admin.css so the logo sits
     in exactly the same spot on the site and in the admin. */
  --logo-x:17px;

  --bg:#000000;
  --card:#141414;
  /* The wall's own ground — see .wall-page. Nothing is drawn in it: it is one
     dark grey glow, brightest under the bar and falling off in a circle to the
     corners, and these are the three greys it passes through from the middle
     of that glow to the far edge. Opaque, because there is nothing underneath
     for them to let through. Neutral by the same rule as everything else here:
     a tint is the quickest way to put a colour back into a wall that is meant
     to be grey. */
  --wall-glow-core:#2b2b2b;
  --wall-glow-mid:#1a1a1a;
  --wall-glow-edge:#0a0a0a;
  /* The squares ruled over the glow. This is as faint as a line can be and
     still exist — at a glance the wall is blank, and it is only once the wall
     moves under you that the grid says how far it moved. Any stronger and it
     stops being a wall and becomes graph paper, which is a surface with an
     opinion about where things go. */
  --wall-grid:rgba(255,255,255,.036);
  /* What shows in the sliver of time before the gradient is painted, and the
     colour the wall averages to. */
  --wall-bg:#171717;
  --card-2:#1c1c1c;
  --ink:#f5f5f5;
  --ink-dim:#a3a3a3;
  --ink-faint:#6b6b6b;
  --line:rgba(255,255,255,.07);
  --line-hi:rgba(255,255,255,.15);
  /* Rejection red, and actually red. It used to be a grey, which worked while
     a card was flat black and disappeared the moment the card was lit — the
     one mark on the page that has to be seen was the one that could not be.

     Then it was #ff2f45, a fully saturated signal red that overcorrected: a
     mistyped digit looked like something had gone wrong with the site. The
     fix is not that red with the saturation pulled out — that only turns it
     rose, which is a quieter wrong colour rather than a right one. It is the
     level a palette designed for dark grounds already puts an error at, so
     this is Radix red-9, the step made for exactly this and no other job.
     Every warning on the site is this one token: the mark on a rejected
     field, the border that flashes with it, the tooltip behind its reason,
     the "Didn't send" line, and the cross that drops the WhatsApp number. */
  --bad:#e5484d;
  /* The one blue on a site that is otherwise ink and paper, and it is only
     ever used to say "this was carried here for you" — the note at the top of
     a form that arrived already written. Kept to the system blue rather than
     a mixed one: on this ground anything softer goes grey, and anything
     brighter reads as a warning, which this is the opposite of. */
  --note:#0a84ff;
  /* And the green that answers --bad. Only ever on the send button, and only
     ever meaning the one thing. */
  --good:#30d158;

  /* Apple-style liquid glass */
  --glass-fill:rgba(255,255,255,.10);
  --glass-border:rgba(255,255,255,.28);
  --edge:rgba(255,255,255,.8);

  --a1:#ededed; --a2:#c9c9c9; --a3:#aeaeae; --a4:#8f8f8f;

  /* The page's content width, and the gutter either side of it. The hero card
     and the grid below both resolve to exactly this, so their edges align. */
  --page:1180px; --gutter:clamp(16px,4vw,40px);

  /* Apple continuous-corner feel: big radii */
  --r-xl:40px; --r:24px; --r-sm:16px; --r-pill:999px;
  --ease:cubic-bezier(.32,.72,0,1); /* Apple spring-ish */
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* Said to a screen reader and to nobody else. Used where the visible answer
   is a colour or a shape — the send button's own result, for one. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
html{scroll-behavior:smooth;scroll-padding-top:72px;font-size:118%} /* default zoom-in — whole page scales up */
/* The page's own scrollbar is hidden: on this dark ground the system one is a
   bright slab down the right edge, and it cuts across the glass. Scrolling
   itself is untouched — wheel, trackpad, keys and the bar's own drag on
   touch devices all still work. The panels that scroll inside the page
   (the country menu, the list on /about) keep their thin styled bars. */
html{scrollbar-width:none}
html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0}
@media (max-width:640px){html{font-size:105%}}
/* The wall does not scroll at all for now — see .wall-page, which is pinned to
   exactly one windowful. Locked here as well as there so that nothing else can
   put a scrollbar on this page either: with nothing pinned up yet there is
   nowhere to go, and a page that scrolls a wall of empty squares past you is
   just a way of saying so twice.

   Scoped with :has so it is this page only. Every other page keeps the
   overflow-x:hidden below and its ordinary vertical scroll. */
body:has(.wall-page){overflow:hidden;overscroll-behavior:none}
body{
  font-family:var(--font-body);
  background:var(--bg);
  color:var(--ink);
  min-height:100vh;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  transition:background .5s var(--ease), color .5s var(--ease);
}
/* ================= QUICK TRANSITIONS ================= */
/* No page-level fade. Nav links are client-side <Link>s now, so pages swap in
   place — a body fade would only make the swap flash black for no reason. */
/* ── ARABIC MODE ──
   Layout stays exactly where it is (no mirroring); only the text
   becomes Arabic and flows right-to-left. */
/* Arabic script connects letters — ANY letter-spacing breaks the joins */
html[lang="ar"] *{letter-spacing:0 !important}
/* buttons stay Avenir in Arabic — never a Latin display face */
html[lang="ar"] .btn,html[lang="ar"] .cta-btn,html[lang="ar"] button{font-family:var(--font-body)}
/* directional arrows flip in RTL */
html[lang="ar"] .cta-btn svg{transform:scaleX(-1)}
html[lang="ar"] .cta-btn:hover svg{transform:scaleX(-1) translateX(3px)}
/* all Arabic headings use SF Pro Arabic */
html[lang="ar"] .hero-text h1,html[lang="ar"] .page-title,
html[lang="ar"] .card h2,html[lang="ar"] .display{font-family:"SF Pro AR",sans-serif}
/* hero card mirrors in Arabic so the photo follows the text side */
html[lang="ar"] .hero-card{flex-direction:row-reverse}
/* text blocks read RTL, but the boxes around them don't move */
html[lang="ar"] .hero-text,html[lang="ar"] .card,html[lang="ar"] footer{direction:rtl}

/* ================= NAV — flat, full width, part of the background ================= */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:100;
  direction:ltr; /* header layout stays put in RTL — only the text translates */
  display:flex;align-items:center;gap:4px;
  /* The bar's height is pinned so the type inside it can be set larger
     without the header itself growing — everything in here is centred in a
     box that does not move. */
  height:49px;
  padding:0 clamp(16px,4vw,40px) 0 var(--logo-x);
  /* The page's own black at 62%, over a real blur — not clear glass. Clear
     glass was tried and it took the header's colour from whatever happened to
     be under it: on the hero it turned blue with the picture, and the links
     lost the one steady ground they had. Black and blurred is the header
     reading as its own surface at the top of the window, on every page and
     over every backdrop, which is what it was before. */
  background:color-mix(in srgb, var(--bg) 62%, transparent);
  backdrop-filter:blur(16px) saturate(160%);-webkit-backdrop-filter:blur(16px) saturate(160%);
  /* The edge is held at transparent rather than left off, so the bar's height
     does not change by a pixel when scrolling brings the line in. */
  border:0;border-bottom:1px solid transparent;
  transition:border-color .4s var(--ease);
}
/* Off the top of the page there is content running under the bar, and the
   hairline is what separates the two. */
.nav.scrolled{border-bottom-color:var(--line)}
.brand{display:flex;align-items:center;font-family:var(--font-body);font-weight:800;font-size:1.2rem;letter-spacing:-.03em;color:var(--ink);text-decoration:none}
.brand i{color:var(--a1);font-style:normal}
/* logo is black — invert on dark. Height matches the 32px nav buttons
   plus a touch of overflow, kept vertically centered with them. */
.brand img{height:46px;width:auto;display:block;margin-block:-8px;filter:invert(1)}
/* centered links, toggles pinned to the end */
.nav-center{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:flex;gap:2px;
}
/* in Arabic the links read right-to-left: الرئيسية first */
html[lang="ar"] .nav-center{direction:rtl}
/* links share the lang button's exact size (32px tall, same type scale).
   The wall's back arrow is one of these too — it is a link in the header like
   any other, so it is lit the same way rather than given a hover of its own to
   drift out of step. Its own size is set further down, at .nav-back. */
.nav-center a,.nav-back{
  display:inline-flex;align-items:center;height:35px;padding:0 15px;
  font-family:var(--font-body); /* header stays Avenir — see FONT-RULES.md */
  color:var(--ink-dim);text-decoration:none;font-size:.92rem;font-weight:700;
  border-radius:var(--r-pill);border:1px solid transparent;
  /* the glass layer lives here, not on :hover — creating/destroying it on
     hover left a dark box behind while the compositor tore it down */
  background:transparent;box-shadow:none;
  /* no backdrop-filter here: always-on blur left a dark plate behind every
     link. The glass lives in ::before, which fades in only on hover — the
     layer still exists at all times, so nothing is torn down mid-transition */
  position:relative;isolation:isolate;
  transition:color .3s var(--ease),border-color .3s var(--ease);
}
.nav-center a::before,.nav-back::before{
  content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;
  background:rgba(255,255,255,.09);
  backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.4), inset 0 -1px 0 rgba(255,255,255,.06), 0 4px 14px rgba(0,0,0,.25);
  opacity:0;transition:opacity .3s var(--ease);
}
/* current page — the brighter text is the whole signal: no outline, no plate,
   so the row reads as plain type until it is hovered */
.nav-center a.active{color:var(--ink)}
/* liquid-glass pill on hover — colour and glass arrive together */
.nav-center a:hover,.nav-back:hover{color:var(--ink);border-color:rgba(255,255,255,.25)}
.nav-center a:hover::before,.nav-back:hover::before{opacity:1}
/* The mark that stands in for a link's word on a phone — off everywhere the
   word fits, which is everywhere but the narrow bar. Same 24-box and the same
   stroke as the language glyph beside it, so the row is one set of marks. */
.nav-ico{
  display:none;width:20px;height:20px;flex:none;
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.nav-end{display:flex;gap:8px;margin-inline-start:auto}
/* translate glyph in the language button */
.lang-ico{width:17px;height:17px;margin-inline-end:6px;opacity:.75;transition:opacity .3s var(--ease), transform .4s var(--ease)}
#lang:hover .lang-ico{opacity:1;transform:rotate(-8deg) scale(1.1)}
.btn{
  display:inline-flex;align-items:center;justify-content:center;height:35px;min-width:35px;padding:0 12px;
  border-radius:var(--r-pill);border:1px solid var(--line);background:transparent;
  color:var(--ink);font-family:var(--font-body);font-size:.9rem;font-weight:700;cursor:pointer;
  /* the language toggle is a link to the other language's page, so it arrives
     underlined unless told otherwise — it is a control, not a sentence */
  text-decoration:none;
  transition:all .3s var(--ease);
}
/* borders hold steady on hover site-wide — only fill/colour move */
.btn:hover{background:var(--line)}
.btn:focus-visible,.nav-center a:focus-visible,.nav-back:focus-visible,.tile:focus-visible,.cta-btn:focus-visible{outline:2px solid var(--a2);outline-offset:2px}
.btn svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* The wall's bar runs the other way: the way out alone at the left, and at the
   right the way to add something with the name in the corner past it. Both
   ends are held at the logo's own inset rather than the wider padding the rest
   of the site gives its right-hand edge — --logo-x is what keeps the mark the
   same distance from the corner here as everywhere else and in the admin, and
   with a bar this empty the two marks belong in the corners, not floated in
   from them. The gap between the plus and the name is the .nav-end gap. */
.nav-bare{padding:0 var(--logo-x)}
/* A wider gap than the 8px the header's own controls sit at: the plus and the
   logo are two different kinds of thing — one is a control, the other is the
   name of the place — and at 8px they read as a pair of buttons. */
.nav-bare .nav-end{align-items:center;gap:20px}

/* The way off the wall: an arrow and no word, at the near end of the bar. It takes the
   header links' shape and their hover whole — the same pill, the same glass
   fading in under it, the same dim ink brightening — and differs only in being
   as wide as a mark rather than as wide as a word.

   The arrow is not mirrored in Arabic like every other arrow on the site.
   Those travel with the text; this one points at a place on the screen, the
   left-hand end of a bar that never mirrors. */
.nav-back{padding:0 14px}
.nav-back svg{width:18px;height:18px;flex:none;stroke:currentColor;fill:none;
  stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
/* the عـ glyph renders in a proper Arabic face */
:root[lang="en"] #lang-label{font-family:var(--font-body);font-size:.95rem;font-weight:600;line-height:1}

/* ================= HERO — full first page ================= */
.hero{
  position:relative;min-height:100svh;
  display:grid;place-items:center;
  padding:90px var(--gutter) 40px;
  overflow:hidden;
  /* ── THE BACKDROP ──
     A wave of blue light breaking out of black, with a warm rim along its
     crest. Set --hero-bg to any other image and it takes its place on the same
     terms, with --scene-show:none alongside it to put the animated waves scene
     away. */
  --hero-bg:url("bg/hero.jpg");
}
/* The backdrop, and the one thing on the page that is a photograph. It also
   stands in for the video: it sits underneath, so it covers the first frames
   and stays put if the clip never plays.

   The file arrives soft and dark — it is a smooth gradient render, so nothing
   is done to it here to blur or dim it. That matters more than it sounds: a
   full-window filter:blur() would sit directly under two backdrop-filter
   surfaces — the nav and the hero card — and every one of their frames would
   have to read through it.

   Two things are still done to it here, both about fitting the window rather
   than about how it looks.

   1. Stretched to the hero rather than cropped to it. The picture is a tall
      one and the hero is a wide one, and `cover` would throw away the two ends
      that matter — the black at the top the header sits on, and the light
      coming up from below it. There is nothing in it with a shape to distort:
      it is a gradient, so filling the box outright keeps the top-to-bottom
      story intact at every window size.
   2. Masked away at its edges. The bottom is the long fade, because that is
      where the page carries on into the grid and the picture has to have
      ended by then rather than stopped — and on this picture that is also
      what stops the brightest part of it running straight into the grid. The
      sides are short fades, so the bands do not meet the window with a hard
      edge. What is left over is the page's own black, which is how the two
      match exactly. */
.hero::before{
  content:"";position:absolute;inset:0;z-index:0;
  background:var(--hero-bg,none) center/100% 100% no-repeat;
  -webkit-mask-image:
    linear-gradient(to bottom, #000 0 44%, rgba(0,0,0,.7) 64%, rgba(0,0,0,.22) 82%, transparent 96%),
    linear-gradient(to right, transparent 0, #000 6% 94%, transparent 100%);
  mask-image:
    linear-gradient(to bottom, #000 0 44%, rgba(0,0,0,.7) 64%, rgba(0,0,0,.22) 82%, transparent 96%),
    linear-gradient(to right, transparent 0, #000 6% 94%, transparent 100%);
  /* both masks have to agree for a pixel to show, which is what makes the
     corners fall away rather than one fade painting over the other */
  -webkit-mask-composite:source-in;mask-composite:intersect;
}
/* The clip, cropped to the hero the same way the image is. Shown at its own
   brightness — no dimming filter. If it ever needs taking down again, dim the
   still and the clip by the same amount or the hero visibly jumps the moment
   the video fades in. */
.hero-video{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
  opacity:0;
  /* Slow enough to read as a dissolve rather than a swap, so the clip arrives
     over the still image instead of replacing it. */
  transition:opacity 1.2s linear;
  pointer-events:none;
}
/* Held back until the clip can actually play, so it fades in over the still
   rather than flashing a black frame first. */
.hero-video[data-ready]{opacity:.55}
/* — Apple-event style scene: dark rim-lit waves with a warm/cool glow — */
.scene{display:var(--scene-show,block);position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
/* the light source behind the horizon */
.scene .glow{
  position:absolute;left:50%;bottom:-6%;width:130%;height:70%;
  transform:translateX(calc(-50% + var(--gx,0px)));
  background:
    radial-gradient(42% 46% at 44% 78%, rgba(255,255,255,.5), transparent 65%),
    radial-gradient(55% 55% at 52% 72%, rgba(235,235,235,.42), transparent 62%),
    radial-gradient(70% 60% at 60% 80%, rgba(200,200,200,.16), transparent 70%);
  filter:blur(14px);
  animation:glowdrift 14s ease-in-out infinite alternate;
}
@keyframes glowdrift{
  0%{opacity:.85;transform:translateX(calc(-50% + var(--gx,0px))) scale(1)}
  100%{opacity:1;transform:translateX(calc(-46% + var(--gx,0px))) scale(1.06)}
}
/* dark hills whose top edge catches the light */
.scene .wave{
  position:absolute;width:160%;aspect-ratio:2.6/1;border-radius:50%;
  background:#0b0b0b;
}
/* prism-edge fringing, like light through a glass cube: warm + cool split */
.scene .w1{
  left:-46%;bottom:-34%;
  box-shadow:
    0 -1px 0 rgba(255,255,255,.85),
    0 -2px 5px rgba(255,255,255,.55),
    0 -5px 14px rgba(235,235,235,.5),
    0 -16px 46px rgba(210,210,210,.26),
    0 -34px 90px rgba(190,190,190,.2),
    0 -60px 150px rgba(180,180,180,.1);
  animation:wavebreathe 12s ease-in-out infinite alternate;
}
.scene .w2{
  right:-52%;bottom:-40%;
  box-shadow:
    0 -1px 0 rgba(255,255,255,.6),
    0 -2px 6px rgba(255,255,255,.4),
    0 -6px 16px rgba(230,230,230,.3),
    0 -20px 60px rgba(200,200,200,.22);
  animation:wavebreathe 15s ease-in-out infinite alternate-reverse;
}
@keyframes wavebreathe{
  0%{transform:translateY(0)}
  100%{transform:translateY(-14px)}
}

/* — THE GLASS CARD (Apple liquid glass, "Clear" variant) — */
/* Clear glass, not frosted: the photo behind stays readable through the card
   instead of being blurred into a wash. Legibility comes from a slight dim of
   the backdrop and a bottom-weighted scrim, not from blur. */
/* The card's box, with nothing drawn on it. The card clips its own contents
   and has to — the glass would otherwise spill past its rounded corners — so
   the signature is hung on this instead, which is the same rectangle and clips
   nothing.

   The tilt under the cursor is here rather than on the card, which is where it
   used to be. The stage is exactly the card's rectangle, so the card leans the
   same amount either way — but with the lean up here the signature is on the
   same sheet of glass and goes with it, instead of sitting still while the card
   moves out from under it. Two things a hand wrote on the same page. The script
   sets --rx and --ry on this element now; see SiteScripts.tsx. */
/* — THE HERO'S SLOT, HOLDING THE CARDS —

   The three columns off /work stand here for now. The wrapper stays because
   the wrapper is the slot — the padding, the centring and the window-height
   floor are all written on it, and the stage inside is measured against what
   is left when those come off.

   The photograph does not. It was here because the glass that used to stand
   in this slot was glass — a panel you see a picture through, which needs a
   picture behind it. What stands here now is five cards with their own ground
   and their own hairline, and a wave of blue light running underneath them
   read as a picture the cards were sitting on top of rather than as the page
   they are set into. So the slot is the page's own black: --hero-bg goes to
   none, which leaves .hero::before painting nothing at all. The picture is
   untouched and still the hero's default — every other page that wants one
   gets it, and this slot gets it back by taking one line out.

   Four things come off it and nothing else changes. The padding goes, because
   the stage inside already pays its own — the bar's height plus a margin at the
   top, a deeper band at the foot — and paying both put the band visibly low.
   The centring goes with it: the stage is a full-width grid of its own and has
   no business being treated as one item placed in the middle of another. And
   the window-height floor goes, because the stage sets its own on a screen big
   enough for three columns and asks for nothing on a screen that is not — the
   hero's 100svh would have held a stacked phone layout open below its own end.

   The clipping stays. The pass hangs in front of the page and swings, but it
   swings inside the middle column, and the hero's overflow is what keeps the
   photograph's masked edges off the sides of the window. */
.hero-cards{padding:0;display:block;min-height:0;--hero-bg:none}
/* The stage no longer has the page to itself, so the band that used to be
   the last of the screen is now the distance to the scrolling band under it,
   which is the same distance and reads as it should. */

.hero-stage{
  position:relative;z-index:1;width:min(var(--page),100%);
  transform:perspective(1200px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
  transition:transform .25s ease-out;
}
.hero-card{
  position:relative;z-index:1;
  width:100%;
  min-height:min(74svh,640px);
  display:flex;align-items:center;gap:clamp(24px,4vw,56px);
  padding:clamp(28px,5vw,64px);
  border-radius:var(--r-xl);
  background:
    linear-gradient(115deg, rgba(255,255,255,.09), transparent 38%, rgba(255,255,255,.04) 72%),
    linear-gradient(to top, rgba(0,0,0,.28), rgba(0,0,0,.06) 55%, transparent),
    rgba(255,255,255,.03);
  /* barely any blur — just enough to soften hard pixel edges behind the text */
  backdrop-filter:blur(2px) saturate(180%) contrast(1.05) brightness(.9);
  -webkit-backdrop-filter:blur(2px) saturate(180%) contrast(1.05) brightness(.9);
  border:1px solid rgba(255,255,255,.42);
  box-shadow:
    0 8px 32px rgba(0,0,0,.18),
    0 30px 70px -30px rgba(0,0,0,.55),
    inset 0 1.5px 0 rgba(255,255,255,.7),
    inset 0 -1px 0 rgba(255,255,255,.18),
    inset 0 -26px 50px -20px rgba(0,0,0,.34),
    inset 3px 0 18px -8px rgba(255,255,255,.4),
    inset -3px 0 18px -8px rgba(255,255,255,.4);
  overflow:hidden;
}

/* frosted micro-grain — real glass is never perfectly clean */
.hero-card::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity:.03;mix-blend-mode:overlay;
}

/* — caustic reflections: diagonal light streaks with faint chromatic fringes — */
.refl{
  position:absolute;inset:0;border-radius:inherit;pointer-events:none;overflow:hidden;
  mix-blend-mode:screen;opacity:.8;
}
.refl::before{
  content:"";position:absolute;inset:-30%;
  background:
    linear-gradient(118deg, transparent 22%, rgba(255,255,255,.14) 30%, rgba(255,255,255,.08) 34%, transparent 42%),
    linear-gradient(118deg, transparent 52%, rgba(255,255,255,.09) 58%, rgba(255,255,255,.06) 61%, transparent 68%),
    linear-gradient(118deg, transparent 74%, rgba(255,255,255,.07) 79%, transparent 86%);
  animation:caustic 11s ease-in-out infinite alternate;
}
.refl::after{
  content:"";position:absolute;inset:-30%;
  background:
    radial-gradient(20% 26% at 18% 16%, rgba(255,255,255,.16), transparent 70%),
    radial-gradient(14% 18% at 78% 30%, rgba(255,255,255,.12), transparent 70%),
    radial-gradient(16% 20% at 55% 78%, rgba(255,255,255,.08), transparent 70%);
  filter:blur(6px);
  animation:caustic 17s ease-in-out infinite alternate-reverse;
}
@keyframes caustic{
  0%{transform:translate3d(-3%,-2%,0) rotate(0.001deg)}
  100%{transform:translate3d(3%,2%,0) rotate(0.001deg)}
}

/* The hero card holds nothing but the name and the line under it. Both start
   at the card's own inner edge, so the glass reads as a page rather than as a
   plaque with something centred on it. */
.hero-card{justify-content:flex-start;text-align:start}
/* There is a picture behind the card now, and the brightest part of it lands
   across the middle of it — which is where the line under the name is. So the
   words carry their own shade: a soft dark pool the size of the text block,
   blurred out to nothing well before the card's edge, sitting behind the type
   and in front of the picture.

   isolation:isolate is what keeps it there — without a stacking context of its
   own, the z-index:-1 below would drop the pool behind the card's glass
   instead of behind its words. It is centred on the block rather than pinned
   to a side, so Arabic gets it in the right place for free. */
.hero-text{min-width:0;flex:0 1 auto;position:relative;isolation:isolate}
.hero-text::before{
  content:"";position:absolute;inset:-30% -22%;z-index:-1;pointer-events:none;
  background:radial-gradient(58% 62% at 50% 50%,
    rgba(0,0,0,.62), rgba(0,0,0,.34) 56%, transparent 78%);
  filter:blur(12px);
}
.hero-text h1{
  font-family:var(--font-display);
  font-size:clamp(3rem,8vw,5.6rem);
  font-weight:800;letter-spacing:-.04em;line-height:1.02;
  margin-bottom:16px;
}
/* period matches the heading — no gradient */
.period{color:inherit}
.hero-text .sub{color:var(--ink-dim);font-size:clamp(1.05rem,1.8vw,1.4rem);font-weight:500;line-height:1.6;max-width:46ch}
.hero-text .sub b{color:var(--ink);font-weight:600}
.hero-badge{
  display:inline-flex;align-items:center;gap:8px;margin-top:22px;
  padding:8px 15px;border-radius:var(--r-pill);
  border:1px solid rgba(255,255,255,.22);
  background:rgba(255,255,255,.08);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-dim);
}
.dot{width:7px;height:7px;border-radius:50%;background:var(--a1);animation:ping 2.4s var(--ease) infinite}
@keyframes ping{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--a1) 60%,transparent)}70%,100%{box-shadow:0 0 0 9px transparent}}

/* ---- AVAILABILITY — the corner that says whether I can take it on ----
   A lit dot, and nothing else. It was a pill with the words in it, and the
   words were saying what the colour already said in a corner that only had
   one thing to say. The words are still in the markup, unpainted, for a screen
   reader and for the tooltip — a colour on its own is never the whole message,
   it is only the quickest half of it.

   It is not a status LED, it is light in the glass: no hue at all, made of the
   same white the card's edges and caustics are made of. A saturated green on a
   surface this deliberately grey is a dashboard part sitting on a sheet of
   glass, and it was the last coloured thing left on the card.

   So the meaning is carried by lit against unlit rather than by green against
   amber. Taking work on: the dot is filled and lit, breathing slowly — the
   invitation. Busy: the same circle hollow, a hairline pressed into the glass,
   lit by nothing and not moving. A ring with no light in it is a fact. */
.avail{
  position:absolute;z-index:2;
  top:clamp(20px,2.6vw,30px);right:clamp(20px,2.6vw,30px);
  display:block;line-height:0;
}
/* the card is mirrored on the Arabic page, so the corner moves with it */
html[lang="ar"] .avail{right:auto;left:clamp(20px,2.6vw,30px)}
.avail-dot{
  display:block;width:9px;height:9px;border-radius:50%;
  /* off-centre highlight, so the dot is a bead of light and not a flat disc */
  background:radial-gradient(circle at 36% 30%,
    rgba(255,255,255,.98), rgba(255,255,255,.62) 52%, rgba(255,255,255,.34));
  /* the same top-lit / bottom-shaded pair the card's own rim uses */
  box-shadow:
    0 0 9px 0 rgba(255,255,255,.34),
    inset 0 .5px 0 rgba(255,255,255,.9),
    inset 0 -.5px 0 rgba(0,0,0,.3);
}
/* Busy: the circle with the light taken out of it. Ring only, pressed in. */
.avail-busy .avail-dot{
  background:none;
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,.32),
    inset 0 1px 0 rgba(255,255,255,.5),
    0 1px 0 rgba(0,0,0,.45);
}
/* Slow enough to be a breath rather than a blink, and it moves the light, not
   the element: animating opacity would promote this to its own layer and snap
   it to the pixel grid, which on a 9px circle is a visible jump. */
.avail-available .avail-dot{animation:avail-breathe 5.2s var(--ease) infinite}
@keyframes avail-breathe{
  0%,100%{box-shadow:
    0 0 7px 0 rgba(255,255,255,.26),
    inset 0 .5px 0 rgba(255,255,255,.9),
    inset 0 -.5px 0 rgba(0,0,0,.3)}
  50%{box-shadow:
    0 0 15px 1px rgba(255,255,255,.6),
    inset 0 .5px 0 rgba(255,255,255,.95),
    inset 0 -.5px 0 rgba(0,0,0,.3)}
}

/* ---- THE SIGNATURE — the name a second time, in my own hand ----
   Hung off the card's bottom corner rather than tucked inside it: a signature
   goes at the edge of the thing it signs, and half over the edge reads as
   signed rather than as laid out. It leans the few degrees a hand leans.

   The writing is drawn into a canvas by Signature.tsx, not played from an
   animated image. Two goes at the latter both ran perfectly while showing
   nothing: an animated image under a referenced SVG filter is filtered once
   and the result kept, and a subtree inside a 3D transform is rasterised once
   and the texture reused — either way the frames advance and the screen does
   not, until something unrelated forces a repaint. The read on the page was a
   mark that appeared from nowhere and twitched when you touched it. A canvas
   we paint ourselves is painted, and there is nothing left to negotiate.

   What the corner holds when nothing has run is the still, painted here: the
   finished name, cropped to the writing rather than carrying the 600×300 frame
   it was drawn on. So --sig is simply how wide the mark is.

   How far off the corner is a fraction of --sig, set so the last stroke clears
   the glass rather than stopping on it. It cannot go much further — past the
   card there is only the hero's gutter before the window, and the hero clips.
   The height is in the size for the same reason: what hangs below the card
   once the lean is counted has to fit in whatever the card left over, and on a
   wide, short window those two cross. Tying the size to the shorter of the two
   dimensions keeps the whole mark in frame at every window shape; on a
   letterbox it simply signs a little smaller.

   What the CSS paints is the *still* — the last frame, on its own — so the
   finished name is there with no JavaScript at all. Signature.tsx swaps the
   writing in over the top of it; when it plays, and how it stops, is there. */
/* The layer of its own is not decoration either. The stage above it carries a
   perspective tilt, and inside a 3D rendering context Chromium rasterises the
   subtree once and reuses the texture for the tilt — which means an animated
   image in there advances its frames and nothing on screen changes, until some
   other thing forces the whole layer to be drawn again. Moving the cursor over
   the glass does; that is why the writing only ever appeared on hover. Given a
   compositing layer of its own the mark is drawn on its own schedule, and the
   animation runs. It stays a child of the stage, so it still leans with it. */
.sig{
  position:absolute;z-index:2;
  --sig:clamp(120px,min(23vw,34svh),400px);
  /* How much room there is between the card and the window on that side: the
     hero's own gutter, plus whatever the card left over once it stopped
     growing at --page. The hero clips, so this is the whole budget. */
  --room:calc(var(--gutter) + max(0px,(100vw - 2 * var(--gutter) - var(--page)) / 2));
  /* And how much there is under it: whatever the hero's bottom padding holds,
     plus half of what the card left of the hero's content box, since the card
     is centred in it. The card is at its min-height on this page, so that is
     the height to reckon from; if its contents ever push it past that, the
     estimate is generous and the clamp below simply stops doing anything. */
  --vroom:calc(max(0px,(100svh - 130px - min(74svh,640px)) / 2) + 40px);
  /* Far enough off the corner for the whole mark to sit outside the glass, or
     as far as the room allows, whichever runs out first. The lean costs about
     six hundredths of the width on each side, and about sixteen of it below,
     before the overhang is counted at all — so that comes off each budget
     before anything is spent. */
  bottom:calc(-1 * max(0px, min(.21 * var(--sig), var(--vroom) - .16 * var(--sig) - 4px)));
  right:calc(-1 * max(0px, min(.37 * var(--sig), var(--room) - .06 * var(--sig) - 4px)));
  transform:translateZ(0) rotate(-20deg);
  will-change:transform;
  pointer-events:none;
}
html[lang="ar"] .sig{
  right:auto;
  left:calc(-1 * max(0px, min(.37 * var(--sig), var(--room) - .06 * var(--sig) - 4px)));
  transform:translateZ(0) rotate(20deg);
}
/* The still underneath, the canvas on top. The canvas starts out completely
   transparent, so what is in the corner before any script has run — and on a
   visit with none at all — is the finished name. */
.sig-line{
  position:relative;display:block;width:var(--sig);aspect-ratio:732 / 354;
  background:url("sign/yaser-still.png") no-repeat center/100% 100%;
}
.sig-ink{position:absolute;inset:0;display:block;width:100%;height:100%}
.sig,.sig *{-webkit-user-select:none;user-select:none;-webkit-user-drag:none}

/* The pair under the intro: one opens the page, one takes it away. Same shape
   and weight as the site's own .btn — pill, hairline border, flat fill, no
   shadow anywhere — so they read as part of the furniture rather than as two
   new kinds of control. */
.about-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}
.about-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  height:40px;padding:0 20px;border-radius:var(--r-pill);cursor:pointer;
  border:1px solid var(--line);background:transparent;
  color:var(--ink);font-family:var(--font-body);font-size:.8rem;font-weight:700;
  /* some of these are links rather than buttons — /about's Contact me is an
     <a> — so the underline has to be taken off here, once, for both */
  text-decoration:none;
  transition:all .3s var(--ease)}
/* borders hold steady on hover site-wide — only fill/colour move */
.about-btn:hover{background:var(--line)}
.about-btn:focus-visible{outline:2px solid var(--a2);outline-offset:2px}
.about-btn svg{flex:none;width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round;transition:transform .3s var(--ease)}
/* Down while the cards are folded away, up once they are out. */
.about-btn[aria-expanded="true"] .chev{transform:rotate(180deg)}
/* Both labels occupy the same cell, so the button is as wide as the longer one
   in whichever language it renders — and stays that width when the state
   flips, instead of nudging the button beside it. */
.about-btn .swap{display:grid;place-items:center}
.about-btn .swap>span{grid-area:1/1}
.about-btn .when-open,
.about-btn[aria-expanded="true"] .when-closed{visibility:hidden}
.about-btn[aria-expanded="true"] .when-open{visibility:visible}
/* The one solid control on the page, because it is the one thing to do here.
   Flat white — the weight alone carries it, with nothing glowing under it. */
.about-btn.primary{background:var(--ink);border-color:var(--ink);color:var(--bg)}
.about-btn.primary:hover{background:color-mix(in srgb,var(--ink) 86%,var(--bg) 14%);
  border-color:color-mix(in srgb,var(--ink) 86%,var(--bg) 14%)}
/* the download arrow dips on hover; only that one — the arrow inside the
   dialog's Continue button travels sideways instead (see .cv-foot below) */
.about-actions .about-btn.primary:hover .btn-icon svg{transform:translateY(2px)}

/* The quiet sibling of a primary button: still a button, still has an edge,
   but plainly second to the one that matters.

   Grey, not black. Beside a flat white Contact me a black pill read as a hole
   in the page rather than as the other of two buttons; a grey one is the same
   object at a lower voice. */
.about-btn.subtle{
  /* The same clear glass every .card and .tile on the page is made of — the
     identical gradient stack, the identical .045 fill, the identical rim. It
     used to be a flat rgba(255,255,255,.13), roughly three times the fill of
     the cards it sits among, and it read as a grey chip dropped onto the page
     rather than as a small piece of the same material.

     If the card recipe below ever changes, this changes with it. */
  background:
    linear-gradient(115deg, rgba(255,255,255,.07), transparent 42%, rgba(255,255,255,.03) 74%),
    linear-gradient(to top, rgba(0,0,0,.22), transparent 60%),
    rgba(255,255,255,.045);
  backdrop-filter:blur(2px) saturate(180%);
  -webkit-backdrop-filter:blur(2px) saturate(180%);
  border-color:var(--line);color:var(--ink);
  /* isolate, so the hover sheet below sits above this fill and under the
     label rather than behind the whole button */
  position:relative;isolation:isolate;
}
/* A card does not change colour on hover — it lifts. A button has to answer
   the cursor, so the light arrives on a sheet of its own that fades in: the
   glass recipe stays exactly as it is underneath, and the swap is one eased
   opacity rather than a background stack cutting to another. */
.about-btn.subtle::before{
  content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;
  background:rgba(255,255,255,.07);
  opacity:0;transition:opacity .3s var(--ease);pointer-events:none;
}
.about-btn.subtle:hover{background-color:rgba(255,255,255,.045);border-color:var(--line-hi)}
.about-btn.subtle:hover::before{opacity:1}
.about-btn.quiet{border-color:transparent;background:transparent;color:var(--ink-dim);
  padding:0 12px;font-weight:600}
.about-btn.quiet:hover{background:transparent;color:var(--ink)}

/* ---- the icon cell, and the spinner that takes its place while busy ----
   Both live in one 15px grid cell, so a click swapping the arrow for the
   spinner cannot change the button's width or nudge the label along. */
.about-btn .btn-icon{position:relative;flex:none;width:15px;height:15px;display:grid;place-items:center}
.about-btn .btn-icon>*{grid-area:1/1}
.about-btn .btn-spinner{
  width:15px;height:15px;border-radius:50%;
  border:2px solid color-mix(in srgb,currentColor 25%,transparent);
  border-top-color:currentColor;
  animation:cv-spin .7s linear infinite;
}
/* visibility rather than display, so the cell keeps its size in both states */
.about-btn .btn-spinner{visibility:hidden}
.about-btn.is-busy .btn-icon svg{visibility:hidden}
.about-btn.is-busy .btn-spinner{visibility:visible}
@keyframes cv-spin{to{transform:rotate(360deg)}}
/* no spin without motion: the ring holds still and breathes instead */
@media (prefers-reduced-motion:reduce){
  .about-btn .btn-spinner{animation:cv-pulse 1.2s ease-in-out infinite}
}
@keyframes cv-pulse{50%{opacity:.35}}

/* ---- the CV button ----
   Its mark is a size up on the site's 15px, and deliberately: it sits after
   a two-letter word, where an icon set to the same weight as the label is
   the only thing keeping the pair from reading as a typo. The cell grows
   with it so the spinner still lands exactly where the arrow was.

   And the arrow goes where it points on hover. Every other mark on the site
   that means "this takes you somewhere" moves that way when you reach for
   it — the one in the dialog's Continue button travels sideways; this one
   leaves for the corner. */
.about-btn.cv-btn .btn-icon,
.about-btn.cv-btn .btn-spinner{width:18px;height:18px}
.about-btn.cv-btn svg{width:18px;height:18px;stroke-width:2}
.about-btn.cv-btn .btn-icon svg{transition:transform .3s var(--ease)}
.about-btn.cv-btn:hover .btn-icon svg{transform:translate(2px,-2px)}
/* Mirrored with the page: on the Arabic side the corner it leaves for is the
   other one, so the arrow points there and travels there. */
html[lang="ar"] .about-btn.cv-btn .btn-icon svg{transform:scaleX(-1)}
html[lang="ar"] .about-btn.cv-btn:hover .btn-icon svg{transform:scaleX(-1) translate(2px,-2px)}

/* ---- and while it is busy, the button is nothing but the spinner ----
   The word goes with the arrow. A ring sitting beside a label that still says
   CV reads as a button with a small problem in the corner of it; a button that
   is plainly one thing, working, reads as the button doing what was asked.

   Nothing leaves the flow — the label only goes to opacity 0, and the icon
   cell keeps its 18px — so the pill is exactly as wide busy as it is at rest.
   Only the ring is out of it, parked dead centre of the whole button.

   Getting from one to the other used to be the hard part, because the ring
   comes up somewhere the arrow never was and any straight cross-fade drew it
   through the word on the way. So the handover is carried by scale rather
   than by timing alone: the word and the arrow drop out quickly, and the ring
   grows into place out of nothing over the beat that follows. There is never
   a still, empty button in the middle of it — something is always arriving —
   and the ring is far too small to read as a smear while the letters are
   still leaving.

   The ring's rotation lives on ::before, not here. A running animation on
   transform beats any transform this rule could set, so the two need separate
   elements: the span scales, the ring inside it turns. */
.about-btn.cv-btn{position:relative}
.about-btn.cv-btn>span:not(.btn-icon),
.about-btn.cv-btn .btn-icon svg{
  /* inherit, never a flat `visible`: the word and the arrow are only undoing
     the generic .is-busy rules above, and a hard `visible` here also undid the
     load gate, which hides the page by hiding <main>. On a refresh that left the
     word and its arrow hanging alone over a blank page until the gate lifted. */
  visibility:inherit;opacity:1;
  /* One rule for both, and no delay on either, so the word and the arrow are
     never a beat apart. They also leave on the SAME curve as everything else
     here: an ease-out on the word and a transform-shared curve on the arrow
     read as two things going at once, which is what showed. */
  transition:opacity .1s linear,transform .3s var(--ease);
}
.about-btn.cv-btn.is-busy>span:not(.btn-icon),
.about-btn.cv-btn.is-busy .btn-icon svg{visibility:inherit;opacity:0}
/* static, so the ring's containing block is the button and not the cell */
.about-btn.cv-btn .btn-icon{position:static}
.about-btn.cv-btn .btn-spinner{
  position:absolute;inset:0;margin:auto;width:18px;height:18px;
  /* the ring itself is ::before now */
  border:0;animation:none;
  /* inherit for the same reason as the word above: it is only overriding the
     generic `.about-btn .btn-spinner{visibility:hidden}`, not the page's */
  visibility:inherit;opacity:0;transform:scale(.6);
  transition:opacity .16s var(--ease),transform .2s var(--ease);
}
.about-btn.cv-btn .btn-spinner::before{
  content:'';position:absolute;inset:0;border-radius:50%;
  border:2px solid color-mix(in srgb,currentColor 25%,transparent);
  border-top-color:currentColor;
  /* no ring turning behind an opacity of zero for the life of the page */
  animation:cv-spin .7s linear infinite;animation-play-state:paused;
}
.about-btn.cv-btn.is-busy .btn-spinner{
  opacity:1;transform:scale(1);
  /* Overlapping the word's exit rather than queued behind it. The old .1s
     wait meant a tenth of a second of empty button between the two, and an
     empty button in the middle of a click is exactly what reads as lag. The
     word is at 60% gone when the ring starts, and the ring is small enough
     at that point not to be seen through the letters. */
  transition:opacity .18s var(--ease) .06s,transform .22s var(--ease) .06s;
}
.about-btn.cv-btn.is-busy .btn-spinner::before{animation-play-state:running}
/* no growing and no turning without motion: it arrives at size and breathes */
@media (prefers-reduced-motion:reduce){
  .about-btn.cv-btn .btn-spinner{transform:none}
  .about-btn.cv-btn .btn-spinner::before{animation:cv-pulse 1.2s ease-in-out infinite}
}

/* ================= CV REQUEST DIALOG ================= */
/* The CV is not a file on a link: the button asks who is asking, and the answer
   is carried into the contact form. Hence a short dialog rather than a page —
   nothing here is worth leaving /about for. */
.cv-scrim{
  /* over the nav (100), under the route progress bar (110) — the bar has to
     stay visible while the dialog hands the page over to /contact */
  position:fixed;inset:0;z-index:105;
  display:grid;place-items:center;padding:20px;
  background:rgba(6,6,6,.66);
  backdrop-filter:blur(10px) saturate(140%);-webkit-backdrop-filter:blur(10px) saturate(140%);
  /* Long enough to read as the page dimming rather than as a light going
     out. The blur behind it lands with the fade, which is the half of this
     that used to snap. */
  animation:cv-fade .3s var(--ease) both;
}
.cv-sheet{
  position:relative;
  width:min(520px,100%);max-height:min(86vh,760px);
  /* head and foot are fixed, and only the middle scrolls — on a short window
     the choices are taller than the sheet, and the way on must never be the
     part that is off the bottom of it */
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--card);border:1px solid var(--line-hi);border-radius:var(--r);
  box-shadow:0 40px 90px -30px rgba(0,0,0,.9);
  text-align:start;
  /* Expo-out, not the site's spring: the sheet is arriving from nowhere
     rather than settling from somewhere, and a curve that overshoots reads as
     a bounce on an object that has no weight yet. It comes further and takes
     longer than it did — a third of a second from 14px was quick enough to be
     a cut with a blur on it. */
  animation:cv-rise .42s cubic-bezier(.16,1,.3,1) both;
}
.cv-head{flex:none;padding:28px 32px 0}
.cv-form{display:flex;flex-direction:column;min-height:0;flex:1 1 auto}
.cv-body{
  flex:1 1 auto;min-height:0;overflow-y:auto;
  /* Two questions, not one long one — the gap between them is what says so,
     and at 15px the note's label sat close enough to the last row of tiles to
     read as a caption on them. */
  display:grid;gap:24px;padding:20px 32px 22px;
  /* the page's own bar is hidden site-wide; this panel keeps a thin dim one of
     its own for the times its content runs past the sheet */
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;
}
.cv-body::-webkit-scrollbar{width:6px}
.cv-body::-webkit-scrollbar-track{background:transparent}
.cv-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:3px}
/* text runs right-to-left inside; the panel itself does not move */
html[lang="ar"] .cv-sheet{direction:rtl}
@keyframes cv-fade{from{opacity:0}}
@keyframes cv-rise{from{opacity:0;transform:translateY(22px) scale(.965)}}

.cv-x{
  position:absolute;top:14px;inset-inline-end:14px;z-index:1;
  width:32px;height:32px;display:grid;place-items:center;
  border:0;border-radius:50%;background:transparent;color:var(--ink-faint);cursor:pointer;
  transition:background .2s var(--ease),color .2s var(--ease);
}
.cv-x:hover{background:rgba(255,255,255,.06);color:var(--ink)}
.cv-x:focus-visible{outline:2px solid var(--a2);outline-offset:2px}
.cv-x svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}

/* one step down from a page title: this is a question, not a heading */
.cv-title{font-size:1.35rem;font-weight:800;letter-spacing:-.02em;line-height:1.25;padding-inline-end:34px}
html[lang="ar"] .cv-title{letter-spacing:0}
.cv-lead{margin-top:9px;color:var(--ink-dim);font-size:.86rem;line-height:1.65}

/* Six tiles, three across, rather than six lines down the sheet. The answer
   is a category, and a grid of single words is read in one glance where a
   stacked list has to be read in order — and the six of them together are now
   shorter than the two sentences that used to sit above them. */
.cv-picks{border:0;display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.cv-picks legend{margin-bottom:9px;font-size:.85rem;font-weight:600;color:var(--ink);line-height:1}
/* Rectangles, and the whole rectangle is the control. Wide and shallow: what
   is inside is one word on one line, so the height only has to be enough to
   press — anything past that is empty ground stacked seven times, which is
   what pushed the note and the buttons off the bottom of the sheet.

   A set height rather than a ratio, so every tile is the same rectangle
   whatever its width: the ratio made the full-width one three times the
   height of the rest, and made all of them grow and shrink with the sheet.
   Four above the fields' own 38, which is enough to read as something pressed
   rather than something typed into.

   The corner is a shade tighter than the fields' 10, for the same reason: at
   12 on a rectangle this shallow the ends had started to turn into a pill. */
.cv-pick{
  position:relative;display:grid;place-items:center;text-align:center;
  min-height:42px;padding:6px 10px;
  border:1px solid var(--line-hi);border-radius:8px;cursor:pointer;
  background:rgba(255,255,255,.03);color:var(--ink-dim);
  font-size:.86rem;font-weight:600;line-height:1.25;
  /* one word, and it stays one word — a tile that wraps is not a tile */
  hyphens:none;overflow-wrap:normal;
  transition:border-color .2s var(--ease),background .2s var(--ease),color .2s var(--ease);
}
/* The radio is under the tile rather than beside the word: a dot saying
   "chosen" inside a box that is already drawn as chosen-or-not was the same
   answer given twice. It stays a real input — focusable, arrow-keyed, and
   named to a screen reader — it is only never painted. */
.cv-pick input{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  border:0;clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}
/* the way out, under the six and across all of them */
.cv-pick.is-wide{grid-column:1/-1}
.cv-pick:hover{background:rgba(255,255,255,.05);color:var(--ink)}
/* Chosen is the tile itself: it comes up off the ground and takes the ink. */
.cv-pick:has(input:checked){
  border-color:rgba(255,255,255,.45);background:rgba(255,255,255,.09);color:var(--ink);
}
.cv-pick:has(input:focus-visible){border-color:rgba(255,255,255,.45);box-shadow:0 0 0 3px rgba(255,255,255,.12)}
/* nothing chosen when Continue was pressed: every box carries the mark, since
   the answer is the set rather than any one of them */
.cv-picks.is-bad .cv-pick{border-color:color-mix(in srgb,var(--bad) 55%,transparent)}
.cv-picks.is-bad legend{color:var(--bad)}

/* The ask is the label; "(optional)" is a note on it, and is set below it so
   the two are not read at the same weight. */
.cv-opt{color:var(--ink-faint);font-weight:500}
/* shorter than the contact form's box: a note, not a message */
.cv-note textarea{min-height:74px}
.cv-note.is-bad textarea{border-color:var(--bad)}
.cv-why{color:var(--bad);font-size:.8rem;font-weight:600;line-height:1.3}
/* empty most of the time, and it must not take a line then */
.cv-why:empty{display:none}

/* the bar the sheet ends on: it sits still while the choices scroll behind it,
   so a hairline is needed to part the two */
/* The way on sits at the end of the line the sheet is read along — the right
   in English, the left in Arabic — with Cancel just inside it. Flow-relative,
   so the pair turns over with the language rather than being pinned to one
   side of the window. */
.cv-foot{
  flex:none;display:flex;align-items:center;justify-content:flex-end;
  flex-wrap:wrap;gap:10px 14px;
  padding:16px 32px 20px;border-top:1px solid var(--line);
}
.cv-acts{display:flex;align-items:center;gap:10px}
/* and when there is something to say about why nothing happened, it takes the
   other end of the same bar rather than pushing the buttons off it. It is
   written after the buttons — it belongs to them — so it is ordered back in
   front of them here, and the auto margin between the two is what holds the
   buttons at the end. */
.cv-why{order:-1;margin-inline-end:auto}
/* this arrow travels along the line rather than dipping, and turns over in
   Arabic like every other directional arrow on the site */
.cv-foot .about-btn.primary:hover .btn-icon svg{transform:translateX(3px)}
html[lang="ar"] .cv-foot .about-btn .btn-icon svg{transform:scaleX(-1)}
html[lang="ar"] .cv-foot .about-btn.primary:hover .btn-icon svg{transform:scaleX(-1) translateX(3px)}

/* On a phone it becomes a sheet on the bottom edge: it arrives from the side of
   the screen the thumb is on, and the panel gets the full width to hold six
   choices without cramping them. */
@media (max-width:640px){
  .cv-scrim{place-items:end stretch;padding:0}
  .cv-sheet{
    width:100%;max-height:92vh;
    border-radius:24px 24px 0 0;border-bottom:0;
    animation:cv-up .34s var(--ease) both;
  }
  .cv-head{padding:24px 20px 0}
  .cv-body{padding:18px 20px 20px}
  /* Full width and in reading order: the way on first, the way out under it.
     Both are 44px, the smallest a thumb should have to aim at. */
  .cv-foot{padding:14px 20px calc(16px + env(safe-area-inset-bottom))}
  .cv-acts{display:grid;grid-template-columns:1fr;gap:6px;width:100%}
  .cv-acts .about-btn{width:100%;height:44px}
  /* Side by side, Continue comes last because the end of the line is where it
     belongs. Stacked, that would bury it under Cancel — so on this width it
     goes back on top, which is the end of a column read downwards. */
  .cv-acts .about-btn.primary{order:-1}
}
@keyframes cv-up{from{transform:translateY(100%)}}
@media (prefers-reduced-motion:reduce){
  .cv-scrim,.cv-sheet{animation:none}
}

/* ================= INNER PAGES (about / contact / resume) ================= */
.page{width:min(1220px,100%);margin-inline:auto;padding:130px 20px 60px;position:relative;z-index:1}
.page-title{font-family:var(--font-display);font-size:clamp(2.2rem,5.5vw,3.4rem);font-weight:800;letter-spacing:-.03em;margin-bottom:12px}
.page-sub{color:var(--ink-dim);max-width:62ch;line-height:1.65;margin-bottom:38px}
html[lang="ar"] .page{direction:rtl}
html[lang="ar"] .page-title{letter-spacing:0}

/* contact form — shadcn/ui field styling on the site's dark surface */
.form{display:grid;gap:18px}
.form .row{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.field{display:grid;gap:8px}
.field label{font-size:.85rem;font-weight:600;color:var(--ink);line-height:1}
.field input,.field textarea{
  height:38px;width:100%;
  /* a dark scrim, not a white one: on the contact card these run the full width
     and their right-hand end sits in the brightest part of the card's light,
     where a near-transparent field left the typing unreadable */
  background:linear-gradient(to top, rgba(0,0,0,.42), rgba(0,0,0,.24));
  backdrop-filter:blur(8px) saturate(140%);
  -webkit-backdrop-filter:blur(8px) saturate(140%);
  border:1px solid var(--line-hi);border-radius:10px;
  padding:0 13px;color:var(--ink);font-family:inherit;font-size:.88rem;
  box-shadow:0 1px 2px rgba(0,0,0,.2);
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
/* White, held back — not grey. The muted greys are ink colours, tuned to be
   read against flat black; over a lit card and a blurred dark field they went
   to nothing, and a placeholder nobody can read is a field with no label at
   all. A low-opacity white keeps its distance from the typed answer without
   disappearing under the light. */
.field input::placeholder,.field textarea::placeholder{color:rgba(255,255,255,.6)}
/* shadcn focus ring */
.field input:focus-visible,.field textarea:focus-visible{
  outline:none;border-color:rgba(255,255,255,.45);
  box-shadow:0 0 0 3px rgba(255,255,255,.12);
}
.field textarea{height:auto;min-height:150px;padding:11px 14px;resize:vertical;
  /* the system bar is a white slab on this dark field — thin and dim instead,
     the same treatment the country menu gets */
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent}
.field textarea::-webkit-scrollbar{width:6px}
.field textarea::-webkit-scrollbar-track{background:transparent}
.field textarea::-webkit-scrollbar-thumb{background:rgba(255,255,255,.16);border-radius:3px}
/* ---- iOS Safari's focus zoom ----
   Mobile Safari zooms the page in on any text control it focuses whose text
   measures under 16px, and does not zoom back out when the field is left. On
   a phone the site's root type is fluid and lands these fields at about
   14.8px, which is under the line — so tapping Name threw the page in, and
   every field after it was filled in on a page that no longer fit the screen.

   Nothing about the page is wrong, and Safari cannot be asked not to do it.
   It can be forbidden — user-scalable=no, or maximum-scale=1 in the viewport
   tag — but that takes pinch-zoom away from everyone for the rest of the
   visit, which is a real loss for anyone who needs it and a poor trade for a
   form. The one fix that costs nothing is to clear the bar: on a touch screen
   these controls are set at exactly 16px. They are a hair larger there than
   the design asks for, on the one kind of screen where slightly larger type
   in a form is not a problem in the first place. */
@media (pointer:coarse){
  .field input,.field textarea,
  .phone-field input[type="tel"],.phone-search,
  .pay-coupon-row input,
  input[type="text"],input[type="email"],input[type="tel"],input[type="number"],
  input[type="search"],input[type="url"],input[type="password"],
  textarea,select{
    font-size:16px;
  }
}

/* validation, in place of the browser's own bubble: a solid red edge, and the
   reason behind a small mark rather than a line of text that shifts the page */
.field{position:relative}
/* The red edge is a flash, not a state: it announces the rejection and then
   settles back, leaving the small mark as the lasting explanation. Two
   alternating class names so a repeat submit replays the animation. */
.field.flash-a > input,.field.flash-a > textarea,
.field.flash-a > .phone-field{
  animation:badFlashA 1.5s var(--ease) both;
}
.field.flash-b > input,.field.flash-b > textarea,
.field.flash-b > .phone-field{
  animation:badFlashB 1.5s var(--ease) both;
}
/* border alone — no ring, no glow */
@keyframes badFlashA{
  0%{border-color:var(--line-hi);box-shadow:none}
  8%,55%{border-color:var(--bad);box-shadow:none}
  100%{border-color:var(--line-hi);box-shadow:none}
}
@keyframes badFlashB{
  0%{border-color:var(--line-hi);box-shadow:none}
  8%,55%{border-color:var(--bad);box-shadow:none}
  100%{border-color:var(--line-hi);box-shadow:none}
}
/* The same flash, in the note blue, and for the opposite reason: nothing here
   is wrong. The form arrived already written from the CV dialog, and these are
   the three lines it could not write — so they say so once, on the way in, in
   the one blue the site keeps for "this was carried here for you".

   No fill mode, unlike the red: focus lands in the first of these fields the
   moment the page settles, and an animation that holds its last frame would
   keep the focus ring's border-colour from ever applying. */
.field.nudge > input,
.field.nudge > .phone-field{
  /* Held back until the page itself has arrived. The page enters over .55s,
     and a flash that starts with it is already blue by the time anything is
     legible — which reads as three fields that came in blue and faded, the
     opposite of the point. It waits for the page to land, and only then does
     the blue come up out of nothing and go back to it. */
  animation:noteFlash 1.5s var(--ease) .62s;
}
@keyframes noteFlash{
  0%{border-color:var(--line-hi)}
  8%,55%{border-color:var(--note)}
  100%{border-color:var(--line-hi)}
}
@media (prefers-reduced-motion:reduce){
  .field.flash-a > input,.field.flash-a > textarea,
  .field.flash-a > .phone-field > input[type="tel"],.field.flash-a .phone-flag,
  .field.flash-b > input,.field.flash-b > textarea,
  .field.flash-b > .phone-field,
  .field.nudge > input,.field.nudge > .phone-field{animation:none}
}
/* room for the mark, so it never sits on top of what is being typed */
.field.is-bad > input,.field.is-bad > .phone-field > input[type="tel"]{padding-inline-end:32px}
.field-why{
  position:absolute;inset-inline-end:10px;top:19px;translate:0 -50%;
  width:17px;height:17px;display:grid;place-items:center;
  /* a solid disc rather than an outline: the mark sits on a field that may be
     over the darkest or the brightest part of the card's light, and a stroke
     alone had nothing to hold it against the bright end */
  color:var(--bad);cursor:help;z-index:3;
}
.field-msg .field-why{top:19px}
.field-why svg{width:17px;height:17px;
  stroke-linecap:round;stroke-linejoin:round}
.field-why svg .disc{fill:currentColor}
.field-why svg .mark{fill:none;stroke:#fff;stroke-width:2.2}
/* the tooltip itself — content comes from data-why, so there is one copy.
   It sits beside the mark, and ::before is the little notch pointing back at
   it, drawn as a rotated square of the same red rather than a glyph. */
.field-why::after{
  content:attr(data-why);
  position:absolute;top:50%;inset-inline-start:calc(100% + 9px);
  /* One line, always: every one of these is a single short sentence, and broken
     over two lines the tooltip read as a paragraph of complaint rather than as a
     label on the field. */
  width:max-content;white-space:nowrap;padding:6px 10px;
  background:var(--bad);border-radius:8px;
  color:#fff;font-size:.76rem;font-weight:500;line-height:1.4;text-align:start;
  /* It opens over whatever the card has beside the field — on /contact that is
     the envelope. The shadow and the dark rim are what lift it off that: solid
     red on its own read as part of the picture behind it. */
  border:1px solid rgba(0,0,0,.35);
  box-shadow:0 10px 26px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
}
.field-why::before{
  content:"";position:absolute;top:50%;inset-inline-start:calc(100% + 5px);
  width:8px;height:8px;background:var(--bad);border-radius:1px;rotate:45deg;
}
.field-why::after,.field-why::before{
  translate:0 -50%;z-index:4;
  opacity:0;visibility:hidden;transform:translateX(-3px);
  transition:opacity .18s var(--ease),transform .18s var(--ease),visibility .18s;
  pointer-events:none;
}
/* the notch is already rotated, so it only fades — a transform here would
   travel along the rotated axis */
.field-why::before{transform:none}
.field-why:hover::after,.field-why:focus-visible::after,
.field-why:hover::before,.field-why:focus-visible::before{
  opacity:1;visibility:visible;transform:none;
}
@media (prefers-reduced-motion:reduce){.field-why::after,.field-why::before{transition:none}}
/* A phone has no room for a line that long beside the mark — off the edge of
   the screen is where it would go, and the page cannot scroll sideways to it.
   So it opens back over the field instead, which is wide enough to hold it. */
@media (max-width:760px){
  .field-why::after{inset-inline-start:auto;inset-inline-end:calc(100% + 9px)}
  .field-why::before{inset-inline-start:auto;inset-inline-end:calc(100% + 5px)}
  .field-why::after{transform:translateX(3px)}
}
/* country picker + number, sharing one row. Custom menu rather than a native
   <select>: the closed control shows the flag alone, and the dial code rides
   along in a hidden input. */
.phone-field{
  position:relative;display:flex;align-items:center;direction:ltr;
  height:38px;
  /* the same dark scrim every other field is filled with — a light fill here
     made this one row read as a different control against the card's light */
  background:linear-gradient(to top, rgba(0,0,0,.42), rgba(0,0,0,.24));
  backdrop-filter:blur(8px) saturate(140%);
  -webkit-backdrop-filter:blur(8px) saturate(140%);
  border:1px solid var(--line-hi);border-radius:10px;
  box-shadow:0 1px 2px rgba(0,0,0,.2);
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
/* the number and the flag share one box, so each is bare inside it and the
   focus ring belongs to the box */
.phone-field input[type="tel"]{
  flex:1 1 auto;min-width:0;height:100%;padding:0 13px 0 4px;
  background:none;border:0;border-radius:0;box-shadow:none;
  /* the blur is the box's now, not the number's. Left on, it backed the input
     with a second filtered layer and drew a paler rectangle inside the field */
  backdrop-filter:none;-webkit-backdrop-filter:none;
}
.phone-field input[type="tel"]:focus-visible{outline:none;border:0;box-shadow:none}
.phone-field:focus-within{
  border-color:rgba(255,255,255,.45);box-shadow:0 0 0 3px rgba(255,255,255,.12);
}
/* dial code + number: one left-to-right run in both languages, so the country
   code never ends up on the far side of the number it belongs to */
.phone-number{display:flex;align-items:center;flex:1 1 auto;min-width:0;direction:ltr}
/* the ruler the number's width is measured against — never drawn */
.phone-mirror{position:absolute;top:0;left:-9999px;white-space:pre;visibility:hidden;pointer-events:none}
/* In Arabic the picker moves to the other end of the row, but the number
   itself is still written left-to-right — only its resting edge changes. */
html[lang="ar"] .phone-field{direction:rtl}
html[lang="ar"] .phone-number{justify-content:flex-end}
/* width comes from the size attribute (this country's mask) rather than from
   flex, so the pair stays together against the right edge */
html[lang="ar"] .phone-field input[type="tel"]{
  flex:0 1 auto;width:auto;text-align:left;
  /* no room on the flag side beyond the caret the measured width already
     allows — the 13px that pads the field's outer edge in English would
     otherwise sit between the number and the flag */
  padding:0 0 0 4px;
}
/* and the picker closes the rest of the way in, so the number sits as near
   the flag as the dial code does in English */
html[lang="ar"] .phone-flag{padding-inline:11px 4px}
.phone-flag{
  flex:0 0 auto;display:flex;align-items:center;gap:5px;
  height:100%;cursor:pointer;
  /* logical, so the wide side stays against the field's edge and the narrow
     side against the number in both directions */
  padding-block:0;padding-inline:11px 7px;
  background:none;border:0;color:var(--ink);
}
.phone-flag:focus-visible{outline:none}
.phone-flag:focus-visible .flag{box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),0 0 0 2px rgba(255,255,255,.45)}
/* the dial code reads as part of the number, not as a second control */
.phone-dial{
  flex:0 0 auto;color:var(--ink-dim);font-size:.88rem;direction:ltr;
  padding-inline-end:2px;
}
.flag{width:22px;height:16px;border-radius:2px;display:block;flex-shrink:0;object-fit:cover;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
.phone-flag .chev{width:13px;height:13px;fill:none;stroke:var(--ink-faint);
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .2s var(--ease)}
.phone-flag[aria-expanded="true"] .chev{transform:rotate(180deg)}
/* the menu itself */
/* .phone-field carries a backdrop-filter, and a backdrop-filter makes a
   stacking context — so the menu's z-index below is spent inside that box and
   never reaches the fields under it. Lifting the field itself is what gets the
   menu over the subject and the message; :has means it only lifts while the
   menu is actually open. */
.field:has(.phone-menu),
.phone-field:has(.phone-menu){z-index:60}

.phone-menu{
  position:absolute;top:calc(100% + 6px);inset-inline-start:0;z-index:40;
  width:min(280px,100%);max-height:240px;overflow-y:auto;
  margin:0;padding:6px;list-style:none;
  background:#131313;border:1px solid var(--line-hi);border-radius:12px;
  box-shadow:0 18px 40px -18px rgba(0,0,0,.8);
  /* the system scrollbar is a white slab on this dark panel — thin, dim and
     trackless instead, so the list ends at its own edge */
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;
}
/* Dropped upward when the field is too near the foot of the page for the
   list to fit under it — see PhonePicker. */
.phone-menu.is-up{top:auto;bottom:calc(100% + 6px)}
.phone-menu::-webkit-scrollbar{width:6px}
.phone-menu::-webkit-scrollbar-track{background:transparent}
.phone-menu::-webkit-scrollbar-thumb{
  background:rgba(255,255,255,.16);border-radius:3px;
}
.phone-menu::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.28)}
.phone-menu ul{margin:0;padding:0;list-style:none}
.phone-menu li{margin:0}
/* 240-odd countries need a filter */
.phone-search{
  width:100%;height:34px;margin-bottom:6px;padding:0 10px;
  background:rgba(255,255,255,.04);border:1px solid var(--line-hi);border-radius:8px;
  color:var(--ink);font-family:inherit;font-size:.84rem;
}
.phone-search:focus-visible{outline:none;border-color:rgba(255,255,255,.35)}
.phone-menu-empty{padding:10px;color:var(--ink-faint);font-size:.82rem}
.phone-menu button{
  width:100%;display:flex;align-items:center;gap:10px;
  padding:7px 9px;border:0;border-radius:8px;background:transparent;cursor:pointer;
  color:var(--ink-dim);font-family:inherit;font-size:.84rem;text-align:start;
  transition:background .15s var(--ease),color .15s var(--ease);
}
/* every highlight state is the same grey — no system accent blue anywhere */
.phone-menu button:hover,.phone-menu button:focus,.phone-menu button:focus-visible,
.phone-menu button:active,.phone-menu button.is-on{
  background:rgba(255,255,255,.06);color:var(--ink);outline:none;
}
.phone-field ::selection{background:rgba(255,255,255,.16);color:var(--ink)}
.phone-field{accent-color:#8a8a8a}
.phone-menu-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.phone-menu-dial{color:var(--ink-faint);direction:ltr}
/* ---- taking the number away again ----
   The other half of the offer: a line the visitor added is a line they can
   drop. It stands outside the field's border, at the end of the row — inside
   it, it shared a corner with the mark that explains a rejected number, and
   the two sat on top of each other. Out here the field keeps its own edge and
   the cross is plainly about the whole line rather than about the number in
   it.

   Red under the pointer, because red is what the press means: this is the one
   control on the form that takes something away. Not red at rest, where it
   would be a warning about a number nobody has objected to. */
.field-phone{display:flex;align-items:center;gap:10px}
/* The same row, one field earlier: the address and the mark that opens a
   number beside it. Identical gap and alignment, so the WhatsApp mark and the
   cross that later replaces the row sit on the same vertical. */
.email-row{display:flex;align-items:center;gap:10px}
.email-row > .field{flex:1 1 auto;min-width:0}
/* The control gives up the width the cross takes rather than overflowing the
   column — at the narrow end of the page the field is the whole form wide and
   there is no outside for the cross to stand in. */
.field-phone > .phone-field{flex:1 1 auto;min-width:0}
.phone-drop{
  position:relative;flex:0 0 auto;
  /* 30px is the target a finger gets. The red it fills with is smaller than
     that — see ::before — so the square stays close around the cross rather
     than standing off it, and the button still takes a thumb. */
  width:30px;height:30px;display:grid;place-items:center;
  padding:0;background:none;border:0;cursor:pointer;
  color:var(--ink-faint,var(--ink-dim));opacity:.7;
  transition:opacity .2s var(--ease),color .2s var(--ease);
}
/* The ground, inset from the target it sits in and painted only on hover. */
.phone-drop::before{
  content:'';position:absolute;inset:4px;border-radius:7px;
  background:transparent;transition:background .2s var(--ease);
}
/* Above the ground, and the size it always was — the square shrank, the mark
   did not. */
.phone-drop svg{position:relative;width:17px;height:17px;fill:none;
  stroke:currentColor;stroke-width:2;stroke-linecap:round}
.field-phone:hover .phone-drop{opacity:.9}
/* Under the pointer it becomes the same object the rejection mark is: the
   red filled, the mark cut white out of it. The mark is a disc and this is a
   rounded square, which is the one difference between them — a mark reports,
   this one is pressed. */
.phone-drop:hover,.phone-drop:focus-visible{opacity:1;color:#fff}
.phone-drop:hover::before,.phone-drop:focus-visible::before{background:var(--bad)}
/* The ring belongs to the red square rather than to the target around it, and
   is offset far enough to leave a dark gap — flush against it, a red ring on
   a red box only reads as a bigger box. */
.phone-drop:focus-visible{outline:none}
.phone-drop:focus-visible::before{outline:2px solid var(--bad);outline-offset:3px}
/* The mark for a rejected number is positioned against the whole row, and the
   row is wider than the box now — so it is held off by the cross and the gap
   beside it, which puts it back inside the field where it belongs. */
.field-phone > .field-why{inset-inline-end:50px}
@media (prefers-reduced-motion:reduce){.phone-drop{transition:none}}
/* ---- the WhatsApp offer, before it is a line ----
   Not a field and not a button with a name: the mark and a plus, sitting in
   the stack of short fields under the address it is an alternative to. It
   used to say "Add WhatsApp" at the foot of the form, which made the one
   optional thing on the page the widest line in it.

   The two marks carry their own colour rather than currentColor. Under the
   old rule the hover raised `color` to white and set `fill` to green at the
   same time, and the mark went white on the way — a fill interpolating away
   from a currentColor that was itself moving. One property changes here, and
   it changes straight from the resting grey to WhatsApp's green. */
.phone-add{
  flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;gap:3px;
  /* The width the cross takes on the row below, so the field ends in the same
     place whether the number has been asked for or not. */
  width:30px;height:30px;padding:0;line-height:0;
  background:none;border:0;border-radius:8px;cursor:pointer;
  color:var(--ink-faint,var(--ink-dim));
}
.phone-add .pa-app{width:18px;height:18px;flex-shrink:0;
  fill:var(--ink-faint,var(--ink-dim));
  transition:fill .22s cubic-bezier(.4,0,.2,1)}
.phone-add .pa-plus{width:10px;height:10px;flex-shrink:0;fill:none;
  stroke:var(--ink-faint,var(--ink-dim));stroke-width:2.6;stroke-linecap:round;
  transition:stroke .22s cubic-bezier(.4,0,.2,1)}
.phone-add:hover .pa-app,.phone-add:focus-visible .pa-app{fill:#25d366}
.phone-add:hover .pa-plus,.phone-add:focus-visible .pa-plus{stroke:#25d366}
/* The offer has no box, so a ring drawn tight on the marks would clip them;
   this one is offset out to where the border used to be. */
.phone-add:focus-visible{outline:2px solid rgba(255,255,255,.4);outline-offset:4px}
@media (prefers-reduced-motion:reduce){.phone-add .pa-app,.phone-add .pa-plus{transition:none}}
/* submit uses the same glass cta-btn as the home page */
.form .cta-btn{cursor:pointer}
.form-note{font-size:.8rem;color:var(--ink-faint)}
/* stacked layout: fields, then the message, then the send button */
.form-side{grid-template-columns:1fr;gap:18px;max-width:560px;text-align:start}
.form-fields{display:grid;gap:14px;align-content:start}
.field-msg{grid-template-rows:auto 1fr}
.field-msg textarea{height:100%;min-height:220px}
.form-foot{grid-column:1 / -1;display:flex;align-items:center;justify-content:flex-start;gap:16px;flex-wrap:wrap;margin-top:4px}
/* contact card: extra breathing room around the stacked form, and a box that
   stays identical in English and Arabic — only the content inside flips */
/* The card is the form's box and nothing else. 58% is where the old divider
   ran, so the card ends on the line it used to draw — and past it is the
   page's own black, empty on purpose and waiting for whatever goes there.
   Set as a width rather than a narrower grid span: the span is what the rest
   of the page's cards are measured in, and this is one card sized to its own
   contents, not a card that happens to be three columns of six. */
.contact-card{
  padding:30px clamp(34px,5vw,64px) 34px;align-items:stretch;text-align:start;
  width:58%;
  /* start edge in both directions — the grid flips with dir, so this needs no
     Arabic variant; the card sits under the title on either side. */
  margin-inline-end:auto;
}
/* the home cards hold air under their title on purpose; here the form starts
   straight after it, so that reserved gap is closed. */
/* Full ink, not the faint grey the rest of the site sets a small caps line
   in: this one is the only heading inside the card, and at .68rem in
   --ink-faint it read as a note about the form rather than the name of it. */
/* The card's flex gap already sets the title off from the first field; the
   18px on top of it held the two nearly two lines apart, which read as the
   title belonging to the card rather than to the form under it. */
.card.contact-card > .label{font-size:.68rem;letter-spacing:.2em;color:var(--ink);margin-bottom:0}
/* the page sits higher and the message box is shorter, so the footer is
   already in view without scrolling */

.contact-page{padding:78px 20px 0}
/* The footer sits at z-index 1, so the country list — which hangs below the
   card now that the number is the last line before Send — was painted under
   it. Lifted only while the list is actually open, so nothing about the page
   is stacked differently at rest. */
.contact-page:has(.phone-menu){position:relative;z-index:2}
/* The footer is part of the contact page, not something to go looking for:
   the gap above it is cut right down here so its top edge is on screen with
   the form. */
.contact-page ~ .site-footer{margin-top:clamp(40px,5vw,72px);padding-top:clamp(36px,4vw,56px)}
/* the heading is trimmed on this page so the form and the footer's top edge
   fit the first screen together */
/* the emoji hangs above the card's top edge, so the heading needs clearance
   from it and not just from the card */
.contact-page .page-title{font-size:clamp(1.9rem,4.2vw,2.7rem);margin-bottom:clamp(20px,2.6vw,34px)}
/* On this card the labels are lifted out of the flow (see below), so the row
   .field-msg keeps for one is an empty 8px band under the textarea — which
   put 22px between the message and the line after it where every other gap in
   the form is 14. One row, for the box that is actually in it. */
.contact-card .field-msg{grid-template-rows:1fr}
/* The message box takes the room the page has rather than a fixed 96px: it is
   the one field somebody writes paragraphs into, and on a laptop it was three
   lines tall with a third of the screen empty under the card. Tied to the
   viewport's height, floored so a short window still gets a box worth typing
   in, and capped so a tall screen does not hand it half the page — the card
   has to end above the fold with the footer's top edge showing under it. */
.contact-card .field-msg textarea{min-height:clamp(104px,17vh,156px)}
/* A carried message is four or five lines before the visitor adds anything of
   their own, and a box they have to scroll to read what is already in it reads
   as a box they cannot trust. That used to need its own 150px floor here; the
   box above is taller than that everywhere it matters now, and this rule is
   more specific than the one under the 900px media query — so left in, it
   handed the one message that arrives already written the smallest box on the
   page. The floor lives in the clamp instead. */
/* Every field on one column, in the order they are filled in: name, email,
   phone, then the subject and the message it titles, then the send row. The
   column starts at the card's edge and no field runs the width of the card —
   a name or an email is a few characters, and even the message reads better
   as a column than as a band. Two widths, each 40% off what it was: the short
   fields to what a name actually needs, the message to a paragraph measure. */
.contact-card .form-side{max-width:none;gap:14px;width:100%}
/* The column is the boxes plus a 40px gutter at their end, and that gutter is
   where the two marks live: the one that opens a number, on the address, and
   the cross that later drops it. Written as the column's width minus the
   gutter rather than as a number, so every box stays the same width as every
   other one at any size of screen — on a phone the column is narrower than
   360 and all three shrink together, instead of the two rows with a mark on
   them giving up the mark's width and ending short of the ones without. */
.contact-card .form-fields{max-width:min(100%,360px)}
/* :not, because the number's row already ends in a mark of its own — the row
   runs the full column and the picker inside it gives up the same 40px. */
.contact-card .form-fields > .field:not(.field-phone),
.contact-card .email-row > .field{max-width:calc(100% - 40px)}
.contact-card .field-phone{max-width:100%}
/* Percentage rather than a cap, so it stays in proportion to the card as the
   card grows — with a floor, since a fraction of a phone is not a message box.
   It used to be 46%, held under the divider at 58% so the box and the rule did
   not read as one shape. The card IS 58% now, and 79% of it is the same
   measure on screen the 46% was — the box is unchanged, it is only the thing
   it is a fraction of that got smaller. What is left at its end is the card's
   own margin rather than a second column. */
.contact-card .field-msg{max-width:min(100%,max(300px,79%))}
@media (min-width:900px){
  /* Not a share of the screen but what is left of it. Everything else on this
     page is a fixed height — the heading, the two short fields, the send row,
     the card's own padding — and they come to about 575px above the footer.
     Take those and the ~55px of the footer's top edge that should show under
     the card, and what remains is the message box. So it grows with the window
     and stops where the footer starts, instead of a percentage that happens to
     fit one screen. Floored for short windows, where the sum does not fit and
     a box worth typing in matters more than the footer showing; capped so a
     very tall screen does not hand half the page to one field. */
  .contact-card .field-msg textarea{min-height:clamp(126px,calc(100vh - 674px),316px)}
}
/* placeholders carry the labelling, so the labels are for screen readers only */
.contact-card .field label{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
.form-foot .cta-btn{margin-top:0;padding:11px 20px;font-size:.85rem}
.form-foot .cta-btn:disabled{cursor:default;transform:none;box-shadow:none}
/* Dim only while it is working. Sent is a disabled button too, and dimming
   the one state the visitor is meant to read would be the wrong half of the
   rule above. */
.form-foot .cta-btn:disabled:not(.is-sent){opacity:.55}

/* ---- the send button, which is the whole of the form's answer ----
   Three marks in one cell — the arrow, the tick and the spinner — so the
   button never changes height and nothing inside it moves sideways as the
   state turns over. Same idiom as the CV button on /about: everything
   cross-fades on opacity, because visibility cannot be eased. */
.send-btn .btn-icon{position:static;display:grid;place-items:center;width:16px;height:16px}
.send-btn .btn-icon>*{grid-area:1/1}
.send-btn .btn-icon svg,
.send-btn>span:first-child{transition:opacity .2s var(--ease),transform .3s var(--ease)}
.send-btn .send-tick{opacity:0}
.send-btn.is-sent .send-go{opacity:0}
.send-btn.is-sent .send-tick{opacity:1}
/* The tick is drawn heavier than the arrow it replaces and does not travel on
   hover — it is a statement, not a way on. */
.send-btn .send-tick{stroke-width:3.4;stroke-linejoin:round}
.send-btn.is-sent:hover svg{transform:none}
/* The site mirrors every .cta-btn arrow on the Arabic page, which is right for
   an arrow and wrong for a tick — a backwards tick is not a tick. */
html[lang="ar"] .send-btn .send-tick,
html[lang="ar"] .send-btn:hover .send-tick{transform:none}

/* while it is in flight, the button is the spinner and nothing else */
.send-btn .btn-spinner{
  position:absolute;inset:0;margin:auto;width:17px;height:17px;border-radius:50%;
  border:2px solid color-mix(in srgb,currentColor 25%,transparent);
  border-top-color:currentColor;
  opacity:0;animation:cv-spin .7s linear infinite;animation-play-state:paused;
  transition:opacity .2s var(--ease);
}
.send-btn.is-sending>span:first-child,
.send-btn.is-sending .btn-icon svg{opacity:0}
.send-btn.is-sending .btn-spinner{opacity:1;animation-play-state:running}
@media (prefers-reduced-motion:reduce){
  .send-btn .btn-spinner{animation:cv-pulse 1.2s ease-in-out infinite}
}

/* and then the colour, which is most of the answer.

   Written after .cta-btn:hover as well as over it. The glass hover is a later
   rule at the same weight, so a green button went grey the moment the cursor
   was still on it — which is exactly when it is being read. A button that has
   answered has nothing left to offer a hover, so the answer holds. */
.send-btn.is-sent,
.send-btn.is-sent:hover{
  background:color-mix(in srgb,var(--good) 24%,transparent);
  border-color:var(--good);
  /* White, not green. The label is the one thing here that has to be read at a
     glance and the fill it sits on is already the colour saying "sent" — green
     type on green ground says it twice and reads worse for saying it. */
  color:var(--ink);
  transform:none;
}
.send-btn.is-error,.send-btn.is-blocked,
.send-btn.is-error:hover,.send-btn.is-blocked:hover{
  background:color-mix(in srgb,var(--bad) 20%,transparent);
  border-color:var(--bad);color:var(--ink);
}
/* the error is still a button you can press again, so it keeps its lift */
.send-btn.is-error:hover,.send-btn.is-blocked:hover{transform:translateY(-2px)}

/* ---- the moment it lands ----
   Three things at once, and all of them over in three quarters of a second:
   the pill takes a breath, a ring leaves it, and the tick draws itself in.

   The ring has to be able to leave, so this button does not clip — .cta-btn
   hides its overflow for the sheen it never uses here. */
.send-btn{overflow:visible}
.send-btn.is-sent{animation:send-pop .55s var(--ease)}
.send-btn.is-sent::after{
  content:"";position:absolute;inset:-1px;border-radius:inherit;
  border:2px solid var(--good);pointer-events:none;
  animation:send-ring .75s cubic-bezier(.16,1,.3,1) forwards;
}
/* ~22 units of path, so one dash covers the whole tick and the offset walks
   it on from the corner */
.send-btn .send-tick{stroke-dasharray:22}
.send-btn.is-sent .send-tick{animation:send-draw .45s var(--ease) .1s backwards}
@keyframes send-pop{
  0%{transform:scale(1)}
  38%{transform:scale(1.085)}
  100%{transform:scale(1)}
}
@keyframes send-ring{
  from{opacity:.9;transform:scale(1)}
  to{opacity:0;transform:scale(1.42)}
}
@keyframes send-draw{from{stroke-dashoffset:22}to{stroke-dashoffset:0}}
/* None of it without motion. The colour and the tick are the answer; the
   three seconds of theatre around them are not. */
@media (prefers-reduced-motion:reduce){
  .send-btn.is-sent,.send-btn.is-sent::after,.send-btn.is-sent .send-tick{animation:none}
  .send-btn.is-sent::after{display:none}
}

/* The note at the top of a form that arrived already written, from the CV
   dialog on /about. Phrased as what is already done rather than as an
   instruction, and set in the site's one blue so it is plainly a message
   about the form rather than a field of it.

   Held to the message box's own measure. Nothing else in this column runs the
   width of the card, and this did — it crossed the rule into the column of
   addresses beside it, which read as one banner laid over two things rather
   than as the first line of the form. */
/* .card p sets the body grey and this is a <p> inside a card, so the white
   has to be said at the same weight or louder — hence the card in front. */
.card .form-carried{
  max-width:min(100%,max(300px,46%));
  /* 10px, the fields' own radius, not the card's 16 — it sits directly above
     them and reads as the first row of the form, so it is cut the same way */
  padding:10px 13px;border-radius:10px;
  border:1px solid var(--note);
  background:color-mix(in srgb,var(--note) 14%,transparent);
  color:var(--ink);font-size:.82rem;line-height:1.55;
}

/* contact form result, announced next to the button */
.form-status{
  margin:0;font-size:.82rem;font-weight:600;line-height:1.4;
  opacity:0;transform:translateY(4px);
  transition:opacity .3s ease,transform .3s ease;
}
/* Half-there, and then not there. The button holds the answer for as long as
   it is true; this is the aside next to it, so it waits for the pop to finish,
   says its piece quietly, and takes itself away — a line that stays on a form
   the visitor has finished with becomes furniture. */
.form-status.is-sent{
  transform:none;color:var(--ink-faint);
  animation:sent-aside 4.6s var(--ease) .45s both;
}
@keyframes sent-aside{
  0%{opacity:0}
  10%{opacity:.7}
  78%{opacity:.7}
  100%{opacity:0}
}
.form-status.is-error{opacity:1;transform:none;color:var(--bad);
  /* the status sits low on the card, which is the lit half — the shadow is
     what keeps the red off the orange rather than in it */
  text-shadow:0 1px 3px rgba(0,0,0,.65)}
/* the captcha's refusal, said in the same red as a failed send: to the visitor
   it is the same thing — the message did not go */
.form-status.is-blocked{opacity:1;transform:none;color:var(--bad);
  text-shadow:0 1px 3px rgba(0,0,0,.65)}

/* Turnstile.
   Zero height until Cloudflare actually renders a challenge into it, so the
   ordinary visitor — who is never asked anything — sees no gap above the send
   button where a widget would have been. :not(:empty) is what tells the two
   apart: the box is filled only when there is something to answer. */
.form-guard{grid-column:1 / -1}
.form-guard:not(:empty){margin-top:14px}
@media (prefers-reduced-motion:reduce){
  .form-status{transition:none;transform:none}
}

/* honeypot — off-screen rather than display:none, which some bots skip */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
@media (max-width:640px){
  .form .row,.form-side{grid-template-columns:1fr}
  .field-msg textarea{min-height:130px}
}

/* ================= /CONTACT — THE ENVELOPE =================
   The mark on the contact card, standing in the empty half of it beside the
   form. It replaces the emoji that used to hang over the card's corner, and
   unlike that one it answers: it leans towards the pointer, opens as you come
   near, and a click puts the cursor in the message box.

   Built out of five flat panels in one 3-D space rather than drawn as a
   picture, because the flap has to go BEHIND the pocket when it opens, and
   that is a thing only real depth does honestly. */
.card.contact-card > .env{
  position:absolute;z-index:2;
  inset-inline-end:clamp(20px,6%,88px);top:50%;
  transform:translateY(-50%);
  width:clamp(190px,23%,290px);
  display:block;
  padding:0;border:0;background:none;cursor:pointer;
  color:var(--ink);font-family:inherit;
  -webkit-tap-highlight-color:transparent;
}
.env:focus{outline:none}
.env:focus-visible{outline:2px solid var(--a2);outline-offset:14px;border-radius:20px}
/* A rejected field's reason opens inline-end of its mark, which on this card
   is exactly where the envelope stands — and it was opening underneath it.

   The fix is to take a layer away rather than add one. `.card > *` puts every
   direct child on z-index 2, and a positioned element with a z-index of its
   own is a stacking context: everything inside the form was sealed into layer
   2 with it, so .field-why's z-index 3 could never reach past the envelope.
   Set back to auto, the form stops being a context and the mark and its
   tooltip compete in the card's own stacking order, where 3 and 4 beat the
   envelope's 2 and the reason reads over it.

   Raising the form to 3 instead was the wrong half of the same idea: the form
   is the full width of the card, so putting its box in front of the envelope
   also put it in front for the pointer, and the envelope — which opens on
   hover — stopped being reachable at all. */
.card.contact-card > .form{z-index:auto}

/* the room the envelope stands in — one perspective for everything inside it */
.env-scene{
  position:relative;display:block;width:100%;aspect-ratio:1.34;
  perspective:900px;perspective-origin:50% 40%;
}
/* the object itself: leans to the pointer, and rises a little when open */
.env-body{
  position:absolute;inset:0;transform-style:preserve-3d;
  transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateZ(0);
  transition:transform .5s var(--ease);
}
/* Open is a hover, a keyboard focus, or the class the component sets for the
   moment after a tap — a touch screen never enters or leaves anything, and
   without that third case the envelope would go straight to the field having
   never been seen to open. The first two are :hover and :focus-visible rather
   than more of the same class, so the whole of the pointer case is CSS and
   does not wait on a script.

   Open is not only the flap. The whole object steps forward, grows, and cants
   over a few degrees — one move, on the same curve and the same half-second as
   the flap and the letter, so the envelope reads as being picked up rather
   than as three parts animating at once. */
.env:hover .env-body,
.env:focus-visible .env-body,
.env.is-open .env-body{
  transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) rotate(-5.5deg)
            translateY(-5%) translateZ(22px) scale(1.09);
}
.env-body>*{position:absolute;display:block}

/* — the back panel: the whole rectangle, and the darkest of the three — */
.env-back{
  inset:26% 0 0;border-radius:11px;
  background:linear-gradient(165deg, rgba(196,206,201,.9), rgba(150,162,157,.86));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.5);
}

/* — the letter, in the pocket, rising out of it when the flap lifts — */
.env-letter{
  left:11%;right:11%;top:33%;height:60%;
  border-radius:4px;
  background:linear-gradient(180deg,#fdfefd,#e4eae7);
  box-shadow:0 8px 20px -10px rgba(0,0,0,.8);
  transform:translateY(0) translateZ(2px);
  transition:transform .55s var(--ease);
  padding:15% 14% 0;display:flex;flex-direction:column;gap:11%;
  overflow:hidden;
}
/* three ruled lines, standing for the words not written yet */
.env-letter i{display:block;height:6%;min-height:2px;border-radius:2px;background:rgba(20,34,28,.32)}
.env-letter i:nth-child(2){width:80%}
.env-letter i:nth-child(3){width:52%}
.env:hover .env-letter,
.env:focus-visible .env-letter,
.env.is-open .env-letter{transform:translateY(-46%) translateZ(2px)}

/* — the front pocket: the V-fold, and the piece that hides the letter — */
/* Nearly solid, unlike everything else on this site: a pocket you can see
   straight through is not a pocket, and the letter has to be behind something
   for coming out of it to mean anything. What little translucency is left is
   enough for the card's green to sit under the white. */
.env-front{
  inset:26% 0 0;border-radius:11px;
  transform:translateZ(7px);
  background:linear-gradient(202deg, rgba(244,248,246,.95), rgba(206,216,211,.92) 54%, rgba(228,234,231,.94));
  clip-path:polygon(0 0, 50% 54%, 100% 0, 100% 100%, 0 100%);
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.5);
}
/* the two folds, drawn as one hairline down each side of the V */
.env-front::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(to bottom right, transparent calc(50% - .6px), rgba(0,0,0,.14) 50%, transparent calc(50% + .6px)),
    linear-gradient(to bottom left,  transparent calc(50% - .6px), rgba(0,0,0,.14) 50%, transparent calc(50% + .6px));
  background-size:50% 100%;background-repeat:no-repeat;
  background-position:left top, right top;
}

/* — the flap: hinged on the envelope's top edge, opening backwards — */
.env-flap{
  left:0;right:0;top:26%;height:54%;
  transform-origin:50% 0;
  transform:rotateX(0deg) translateZ(8px);
  transition:transform .55s var(--ease);
  clip-path:polygon(0 0, 100% 0, 50% 100%);
  background:linear-gradient(178deg, rgba(250,252,251,.97), rgba(198,208,203,.93));
  border-radius:11px 11px 0 0;
}
/* Just past flat, so it settles against the back of the envelope rather than
   standing straight up — and past 90° the 3-D sort has already put it behind
   the pocket, with no z-index anywhere in this file doing it by hand. */
.env:hover .env-flap,
.env:focus-visible .env-flap,
.env.is-open .env-flap{transform:rotateX(-166deg) translateZ(8px)}

/* the ground it stands on: a soft pool that tightens as the envelope lifts */
.env-shadow{
  position:absolute;left:14%;right:14%;bottom:-9%;height:16%;
  border-radius:50%;
  background:radial-gradient(50% 50% at 50% 50%, rgba(0,0,0,.62), transparent 72%);
  filter:blur(7px);
  transition:transform .5s var(--ease), opacity .5s var(--ease);
}
.env:hover .env-shadow,
.env:focus-visible .env-shadow,
.env.is-open .env-shadow{transform:translateY(24%) scale(.8);opacity:.6}

/* Below this the form is the width of the card and there is no space beside it
   any more, so the envelope stops being a thing in the margin and becomes the
   card's own mark: centred, above the form, at the size the emoji it replaced
   used to be. */
@media (max-width:900px){
  /* 58% of a phone is not a form, it is a column of one word per line. Below
     the point where there is any room beside the card, the card takes the
     page — the empty half is a wide-screen idea and there is no wide screen
     here to spend on it. */
  .contact-card{width:100%;margin-inline-end:0}
  .card.contact-card > .env{
    position:relative;inset:auto;transform:none;
    width:min(150px,42%);margin:2px auto 10px;
  }
  /* Title, mark, form — the order a card is read in. Written out for all three
     because the envelope is last in the markup, where it belongs for anyone
     tabbing through: the form is the thing on this page, and a decoration that
     opens should not stand between the heading and the first field. */
  .card.contact-card > .label{order:1}
  .card.contact-card > .env{order:2}
  .card.contact-card > .form{order:3}
}

/* Nothing tracks the pointer for anyone who has asked for less movement: the
   two lean angles are pinned to zero here, which the inline values the script
   writes onto the button cannot override — they are inherited, and a
   declaration beats an inherited value. What is left is the opening itself,
   which is an answer to being pointed at rather than an animation running on
   its own, and it arrives at once instead of over half a second. */
@media (prefers-reduced-motion:reduce){
  .env-body{--rx:0deg;--ry:0deg}
  .env-body,.env-letter,.env-flap,.env-shadow{transition:none}
}

/* ================= GRID (after scroll) ================= */
.shell{width:min(calc(var(--page) + var(--gutter) * 2),100%);margin-inline:auto;
  padding:72px var(--gutter) 56px}
/* the row gap is wider than the column gap on purpose: each card's emoji
   hangs above its top edge, and this is the clearance that keeps it off the
   card sitting above it. */
.grid{display:grid;grid-template-columns:repeat(6,1fr);column-gap:20px;row-gap:clamp(48px,4.9vw,78px)}

.card{
  position:relative;border-radius:var(--r);
  /* a little more room at the top than the bottom: the title sits under the
     emoji hanging over the corner, and needs the air to clear it */
  padding:clamp(32px,3vw,44px) clamp(28px,2.6vw,40px) clamp(28px,2.6vw,40px);
  display:flex;flex-direction:column;gap:clamp(16px,1.6vw,22px);
  transition:transform .35s var(--ease);
}
/* the border stays put on hover — only the card itself lifts */
.card:hover{transform:translateY(-2px)}
.card .label{
  font-family:var(--font-display);
  font-size:clamp(1.6rem,2.4vw,2.2rem);font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink);
  position:relative;z-index:3;
}
/* A title sits close to what it titles. The card's own flex gap already
   separates them; this only tops it up, where it used to nearly double it.
   Applies to both kinds of card title: the .label and the h2 the collab card
   leads with, so the two never drift apart. */
.card > .label,.card > h2{margin-bottom:clamp(3px,.5vw,8px)}
/* a title with nothing but its own content under it does not need the gap to
   be held open at all */
.card > .label:last-child,.card > h2:last-child{margin-bottom:0}

.card > *{position:relative;z-index:2}
.card h2{font-family:var(--font-display);font-size:clamp(1.6rem,2.4vw,2.2rem);font-weight:600;letter-spacing:-.02em;color:var(--ink)}
/* a card's h2 is the same thing as a card's .label — the title of the card —
   so it is set the same way: upper case, with the tracking that needs. Arabic
   has no case, so text-transform simply does nothing there. */
.card > h2{text-transform:uppercase;letter-spacing:.04em}
.card p{font-size:clamp(1rem,1.2vw,1.15rem);line-height:1.65;color:var(--ink-dim)}
.span-3{grid-column:span 3}.span-6{grid-column:span 6}
.rows-2{grid-row:span 2}
/* MY GROUP and LET'S COLLAB are the same kind of card and sit one above the
   other, so they are held to one height — otherwise the pair reads as a big
   card and an offcut. The taller of the two sets the number; both then fill
   it. The shorter one keeps its content at the top on the same gap as every
   other card rather than spreading to fill — the air belongs at the bottom of
   the box, not between a title and the thing it titles. */
.card.tall{padding-block:clamp(40px,4vw,58px);min-height:clamp(250px,26vw,340px)}
/* .cta-btn pins itself to the foot of its card, which was invisible while the
   collab card was only as tall as its contents. Held to one height it is not,
   and the button fell away from the title it belongs to — so on these two it
   sits on the same gap every other card's contents do. */
.card.tall > .cta-btn{margin-top:0}
/* centered variant — content sits in the middle instead of hugging one edge */
.card.center{align-items:center;text-align:center;gap:16px}
.card.center .cta-btn{margin-top:10px;align-self:center}

.social{grid-column:span 6;display:grid;grid-template-columns:repeat(5,1fr);gap:12px}
.tile{
  aspect-ratio:1.6;display:grid;place-items:center;border-radius:var(--r-sm);
  color:var(--ink-dim);text-decoration:none;
}
/* the tile itself never moves or changes border, and the mark never resizes —
   hover only brightens it. The mark fills nearly half the tile and still keeps
   clear air at every column width, so it never crowds the edges. */
.tile svg{width:clamp(30px,46%,52px);height:auto;aspect-ratio:1;
  fill:currentColor;color:var(--ink);opacity:.72;
  transition:opacity .3s var(--ease)}
.tile:hover svg,.tile:focus-visible svg{opacity:1}

/* — CLEAR GLASS, shared: the .hero-card recipe for every other card —
   Same key as the hero: next to no blur, a faint fill, and a bright rim. Away
   from the hero the ground is flat black rather than artwork, so the rim and
   the top edge-light carry the shape; the fill stays low so a card never goes
   back to reading as an opaque slab. */
.card,.tile{
  background:
    linear-gradient(115deg, rgba(255,255,255,.07), transparent 42%, rgba(255,255,255,.03) 74%),
    linear-gradient(to top, rgba(0,0,0,.22), transparent 60%),
    rgba(255,255,255,.045);
  backdrop-filter:blur(2px) saturate(180%);
  -webkit-backdrop-filter:blur(2px) saturate(180%);
  /* no lit rim here — the bright edge belongs to the hero card and the footer
     alone. These read as borderless panes; the transparent border only holds
     the 1px the layout was measured against. */
  border:1px solid transparent;
  box-shadow:0 10px 30px -20px rgba(0,0,0,.7);
}

/* — CARD LIGHT, REMOVED —
   The cards used to carry a coloured ramp painted on their own ::before — a
   white heart, a hot hue off the axis and a deep base under both, drifting
   across the card and screened onto the black. It is gone, on every page: a
   card is clear glass on flat black now, and the only colour left is whatever
   the card itself holds. */


/* ==================== HOME — THE LONG PAGE ====================
   Twelve sections rather than a grid of six cards. The band and the two
   full-bleed sections (the statement and the close) sit outside .grid because
   they are the page's own punctuation: they set the rhythm the cards between
   them are read at. */

/* One measure of air, used by everything on this page that follows something
   else: between the work and the playground, the playground and the wall.
   They are steps in one argument, and steps of different sizes read as a page
   assembled out of parts rather than written. (The way out at the foot of the
   page keeps its own, larger, padding — it is the end of the read, not
   another step in it.) */
.home-long{
  --sec-gap:clamp(104px,10.4vw,168px);
  /* the height the work tiles stand to, and the height the wall's empty box
     holds open to match them. One number, so the two cards cannot drift */
  /* The floor is what a phone gets: one tile across the screen, and at 170px
     a 285px-wide tile is a letterbox with a wallet drawn in it. 215 gives the
     paper room to stand. The wall's empty box reads the same number, so the
     two cards cannot drift. */
  --tile-h:clamp(215px,17.5vw,290px);
  padding-bottom:clamp(60px,7vw,110px);
}
/* Only this page. The work grid on /about is a column of a page held to the
   height of the window, and has no room to grow into. */
.home-long .works-grid .shot{min-height:var(--tile-h)}
/* the wider row gap the grid carries by default was clearance for the emoji
   that used to hang over each card's top corner; there are no emoji and no
   six cards any more, so this page sets its own rhythm instead */
.home-long .grid{row-gap:var(--sec-gap)}

/* ---- 2 · the band ----
   One row, two identical sets, translated by exactly half its width — so the
   moment the first set has gone the second is where it started and the loop
   has no seam. CSS only: nothing here needs the marquee script.

   No rules above and below it any more, and no padded strip between them: the
   words run across the same black as everything else, and the only thing that
   marks the band is the words themselves. */
.band{
  overflow:hidden;margin-block:clamp(34px,5vw,72px);
  /* the ends fade rather than cut, so the words arrive and leave */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
/* `reverse` so the words travel left-to-right on the English page, which is
   the direction that page is read in; the Arabic rule below turns it back. */
.band-row{display:flex;width:max-content;animation:band-run 38s linear infinite reverse}
.band-set{display:flex;align-items:center}
.band-item{
  display:inline-flex;align-items:center;white-space:nowrap;
  font-family:var(--font-display);
  font-size:clamp(1.6rem,4vw,3.1rem);font-weight:600;letter-spacing:-.01em;
  color:var(--ink);opacity:.5;
}
.band-dot{
  display:inline-block;width:6px;height:6px;border-radius:50%;
  background:currentColor;opacity:.45;
  margin-inline:clamp(20px,2.6vw,42px);
}
@keyframes band-run{from{transform:translateX(0)}to{transform:translateX(-50%)}}
html[lang="ar"] .band-row{animation-direction:normal}
@media (prefers-reduced-motion:reduce){
  .band-row{animation:none}
  .band{-webkit-mask-image:none;mask-image:none}
}

/* ---- a card title with something on the other end of its line ---- */
.card-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;flex-wrap:wrap}
.card-head > .label{margin-bottom:0}
.card-head .more-link{margin-top:0}
/* Used as the card's own title line, the head takes the title's gap under it —
   otherwise the row it replaced takes the spacing with it. */
.card > .card-head{margin-bottom:clamp(3px,.5vw,8px)}

/* ---- the arrow on the end of a title ----
   A chevron in a ring: the smallest thing that can say a card has an address
   of its own. It sits on the title's line at the far end of it, and it is the
   only part of the head that answers to the cursor. */
/* No ring around it. A chevron in a circle is a button, and this is not a
   button — it is the last character of the title, and it should read as one:
   the mark alone, at the weight of the words beside it. */
.label-go{
  flex:none;align-self:center;display:inline-grid;place-items:center;
  /* Measured in the title's own em rather than in pixels of its own: it is the
     last character of the title and it has to be the height of the ones before
     it, at every window size the title is set at. A number of its own was a
     chevron standing a third taller than the word it followed. */
  width:.95em;height:.95em;
  color:var(--ink-faint);text-decoration:none;
  transition:color .25s var(--ease),transform .25s var(--ease);
}
.label-go svg{width:100%;height:100%}
/* It leads where it points. */
.label-go:hover{color:var(--ink);transform:translateX(2px)}
html[lang="ar"] .label-go:hover{transform:translateX(-2px)}

/* ---- the whole title as the way in ----
   The words and the chevron are one link rather than a label with a target
   parked beside it. An 18px chevron is a hard thing to hit and an easy thing
   to miss entirely, and the title is already the sentence that says where the
   card goes — so the title is what you click, and the arrow is the last
   character of it rather than a control of its own. */
.head-go{
  display:inline-flex;align-items:center;
  /* Against the words, not across the card from them: a space, the width of
     one, rather than the gap between two separate things on a line. */
  gap:.3em;
  text-decoration:none;color:inherit;
}
.head-go .label{margin-bottom:0;transition:color .25s var(--ease)}
/* One hover, one response: the words and the mark answer together, because
   they are one thing. */
.head-go:hover .label-go{color:var(--ink);transform:translateX(2px)}
html[lang="ar"] .head-go:hover .label-go{transform:translateX(-2px)}
.head-go:focus-visible{outline:2px solid var(--line-hi);outline-offset:5px;border-radius:6px}
/* Arabic reads the other way, so the arrow points the other way. */
html[lang="ar"] .label-go svg{transform:scaleX(-1)}

/* ---- a switch on the end of a title ----
   Two words in a pill with the live one lit. It is a switch rather than two
   tabs because there is one strip under it and it only ever shows one list —
   what changes is which list, not how much of the card is in use. Colour does
   the lighting, never opacity: a faded layer on this page is promoted and
   snaps to the pixel grid as it lands. */
.seg{
  flex:none;align-self:center;display:inline-flex;gap:2px;padding:2px;
  border-radius:var(--r-pill);border:1px solid var(--line);
  background:rgba(255,255,255,.03);
}
.seg-btn{
  appearance:none;border:0;cursor:pointer;
  padding:4px clamp(8px,.75vw,12px);border-radius:var(--r-pill);
  background:transparent;color:var(--ink-faint);
  font:inherit;font-size:clamp(.6rem,.62vw,.7rem);font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;line-height:1.4;
  transition:color .25s var(--ease),background .25s var(--ease);
}
html[lang="ar"] .seg-btn{letter-spacing:0}
.seg-btn:hover{color:var(--ink-dim)}
.seg-btn.is-on{color:var(--ink);background:rgba(255,255,255,.10)}
.seg-btn:focus-visible{outline:2px solid var(--line-hi);outline-offset:2px}

/* ---- the swap ----
   A dissolve, not a deal.

   The row used to be thrown: the old one lifted and blurred away, and the new
   one was dropped in a card at a time from eighteen pixels up, each one
   tumbling nine degrees and growing from three-quarter size. Thirteen icons
   doing that at once is a scatter — the eye follows the movement instead of
   the marks, and by the time it settles you have watched an animation rather
   than read a list.

   What replaces it is the smallest move that still says the row changed. The
   leaving row simply softens out where it stands — no lift, no blur, nothing
   travelling. The arriving row comes up through a hair of its own height and
   settles from a hair under full size, left to right on a tight stagger, so
   there is still a direction to it and still a sense of the marks being laid
   down rather than cut to. Nothing rotates, nothing leaps, and every icon
   ends exactly where it began.

   The transform is written in three dimensions on purpose. These are icons
   fading through their own opacity, and a layer that is promoted only for the
   duration of a fade snaps onto the pixel grid on the way in and off it at
   the end; holding the layer for the whole run keeps the edges still. */
.app-strip{transition:opacity .2s var(--ease)}
.app-strip.is-leaving{opacity:0}
@keyframes app-settle{
  from{opacity:0;transform:translate3d(0,6px,0) scale(.965)}
  to  {opacity:1;transform:translate3d(0,0,0) scale(1)}
}
.app-strip.is-arriving .app-icon{
  animation:app-settle .44s cubic-bezier(.22,.61,.36,1) backwards;
  animation-delay:calc(var(--i,0) * 20ms);
  backface-visibility:hidden;
}
/* A swap someone asked not to be animated is still a swap: the new row is
   simply there. */
@media (prefers-reduced-motion:reduce){
  .app-strip{transition:none}
  .app-strip.is-arriving .app-icon{animation:none}
}

/* ---- 5 · the playground ----
   Two sentences and a way in, and nothing else: no card, no tiles, no title.
   The things themselves are on /playground, so this section's whole job is to
   say what is through the door and then be the door.

   It is the only prose on this page: a heading-sized sentence with a quieter
   line under it, set to the width of a paragraph rather than the width of the
   page. It sits on the left with the right side left empty, against the
   cards above and below it that run the full width, so the reader can see at
   a glance that this is the page talking and not another panel of work.

   The Arabic page mirrors it — the block on the right, the empty side on the
   left — because a block written to a margin is written to the margin the
   language starts from. */
.lab{display:grid;grid-template-columns:minmax(0,1fr);row-gap:clamp(22px,3vw,40px)}
/* Which side each block lands on is stated outright — justify-self, against a
   single full-width column — rather than left to an auto margin to work out
   from the space that happens to be left over. Auto margins are the kind of
   thing engines disagree about once direction:rtl is in the mix, and this
   layout is not worth a disagreement: the page is laid out left to right in
   both languages, so start is the left of the page and end is the right, and
   the Arabic page says end. */
.lab-text{
  display:flex;flex-direction:column;gap:clamp(12px,1.4vw,20px);
  align-items:stretch;text-align:left;
  /* a paragraph's measure, not the page's: at full width a sentence this
     size would run to two or three words a line and read as a headline */
  width:min(52ch,100%);justify-self:start;margin:0;
}
/* The mirror, and mirrored on both counts: the block moves to the right, the
   margin Arabic starts its lines from, and the lines inside it are written
   from the right, because that is the direction Arabic reads. */
html[lang="ar"] .lab-text{direction:rtl;text-align:right;justify-self:end}
.lab-text p{
  font-family:var(--font-display);
  font-size:clamp(1.5rem,3.4vw,2.75rem);font-weight:600;
  line-height:1.24;letter-spacing:-.02em;color:var(--ink);margin:0;
}
.lab-text .lab-dim{
  font-size:clamp(1.05rem,1.75vw,1.4rem);font-weight:500;
  line-height:1.5;letter-spacing:0;color:var(--ink-dim);
}
/* The button sits directly under the words and starts where they start: the
   same block, the same measure, and the same margin its lines are written to
   — the left in English, the right in Arabic. Lined up with the beginning of
   the text rather than with the end of it, it reads as the next thing after
   the sentence instead of as a control parked in the corner. */
.lab-act{
  display:flex;justify-content:flex-start;
  width:min(52ch,100%);justify-self:start;margin:0;
}
/* The whole block — both sentences and the button under them — sits on the
   right on the Arabic page, and reads from
   the right inside that. So the button stays in the text's own column and
   lines up with the edge the lines begin at. */
html[lang="ar"] .lab-act{justify-self:end;justify-content:flex-end}
/* .cta-btn pins itself to the foot of the flex card it usually lives in, and
   is sized for the one that ends the page. This one is smaller — it is a way
   through to a side room, not the point of the page — and it is solid white
   rather than glass: the same flat white as the primary button on /about,
   where the weight alone carries it and nothing glows underneath. */
.lab-act .cta-btn{
  margin-top:0;align-self:auto;white-space:nowrap;
  padding:11px 20px;gap:7px;font-size:clamp(.86rem,.95vw,.95rem);
  background:var(--ink);border-color:var(--ink);color:var(--bg);
  backdrop-filter:none;-webkit-backdrop-filter:none;
  box-shadow:0 8px 24px rgba(0,0,0,.25);
}
/* the glass keeps a lit top edge; a solid button has no glass to light */
.lab-act .cta-btn::before{display:none}
.lab-act .cta-btn:hover{
  transform:translateY(-2px);
  background:color-mix(in srgb,var(--ink) 88%,var(--bg) 12%);
  border-color:color-mix(in srgb,var(--ink) 88%,var(--bg) 12%);
  box-shadow:0 14px 34px rgba(0,0,0,.3);
}
.lab-act .cta-btn svg{width:14px;height:14px}
/* The arrow follows the words, and in Arabic the words run the other way, so
   the arrow has to be told to as well — the site already mirrors the glyph,
   but a button laid out left to right puts the mirrored arrow at the head of
   the line instead of at the end of it. Same rule the button at the foot of
   the page is given. */
html[lang="ar"] .lab-act .cta-btn{direction:rtl}
/* Narrow enough and there is no side to take: both blocks fill the column,
   and width:min(52ch,100%) has already done that on its own. */

/* ---- 6 · the wall ----
   The work card's twin, one section down and empty: same width, same glass,
   same head. The whole pane is the link rather than the words in its corner,
   so it is given the height a card with contents would have — a link you can
   only hit along one line of text is a link that misses.

   It is an <a>, so it has to be told it is still a card: no underline, no
   link colour, and the hover the words in the corner would have had if they
   were the link now belongs to the pane. */
.wall-card{text-decoration:none;color:inherit}
.wall-card .card-head{width:100%}
/* An empty box holding open exactly the room the work card's tiles take, so
   the two cards are the same height at every width without either one being
   given a number of its own to drift out of step with. It is the tile row's
   own min-height; the card's padding and gap are the same on both cards
   already, being the same card. */
.wall-fill{display:block;flex:1;min-height:var(--tile-h,clamp(140px,15vw,230px))}

/* ================= THE WALL =================
   A wall with no end to it, and nothing drawn in it.

   It went through a photograph of plaster stretched over the page, then a tile
   cut out of that photograph, then seven layers of SVG turbulence drawing the
   plaster from scratch so it would stay sharp at any zoom and repeat forever.
   All of that was in service of looking like a real surface, and looking like
   a real surface is not what this page is for. The wall is a ground for what
   gets pinned to it; a wall with tooth and trowel marks and a lamp thrown
   across it is competing with the notes rather than holding them.

   So: one dark grey glow. Not a top-to-bottom fade — that is a backdrop, and
   it puts a horizontal band across the page at whatever height the eye decides
   the middle grey sits at. This falls off in a circle instead, thrown from
   just under the bar and dying into the corners, which is how a light in a
   room actually meets a wall: there is a place the light is, and the wall gets
   darker in every direction away from it. It also gives the page a centre,
   which is worth having on a page whose whole job is to be a place to put
   things.

   Nothing tiles, so there is no seam and no repeat period to hide, and nothing
   is sampled, so there is no resolution to run out of — it is as clean at 3x
   and ten screens long as it is at 1x. It is painted on <main> rather than the
   body, which is what lets the main out of the 1220px column to the full width
   of the window, and it scrolls with the page, so a note keeps the patch of
   wall it was put on. (background-attachment:fixed is not available here in
   any case: the page-enter animation's containing block traps it, the same way
   it traps position:fixed.) */
.wall-page{
  width:100%;max-width:none;
  /* Exactly one windowful, and held there: the wall does not scroll in any
     direction for now. It was 280vw by 280dvh and panned in all four, opening
     in the middle — which is right for a wall with things on it and wrong for
     this one, where every direction leads to more of the same empty squares
     and the only thing panning proves is that there is nothing to find.

     These three lines are the whole of it. Give the wall a size larger than
     the window again and the panning comes back with it; the body rule above
     unlocks at the same time. */
  width:100%;height:100dvh;min-height:0;overflow:hidden;
  /* The light sits in the middle of the wall, because the middle is where the
     page opens and where things will be put; it used to be up at the bar, from
     when the wall only went downwards, and now that the wall runs in all four
     directions a light pinned to one edge just means three dark quarters.

     Wider than the wall and taller than it — 150% by 115% — so the circle is
     cut off by the edges of the page rather than closing inside them. A glow
     that completes on screen is a vignette drawn on a wall; one that runs past
     the corners is a wall with a light on it.

     Sized to the wall rather than to the window, so however far the wall grows
     it is lit once, in the middle, and darkens evenly out to every edge —
     rather than a glow repeating every screenful. */
  background-color:var(--wall-bg);
  /* Three layers, topmost first: the two rules that make the squares, then the
     glow. The squares are the only thing on the page with a fixed size, and
     that is the point — the glow is sized to the wall and so it slides at the
     same rate as the wall itself, giving the eye nothing to measure. The grid
     does not scale, so it is what the movement is read against. Small squares
     rather than large: a big square is architecture — it divides the wall into
     places and starts telling you where a thing ought to go — where a small
     one is just a weave, and the wall stays somewhere you can put anything
     anywhere. Small also means a short flick crosses several of them, which is
     the whole job. */
  background-image:
    linear-gradient(90deg, var(--wall-grid) 1px, transparent 1px),
    linear-gradient(180deg, var(--wall-grid) 1px, transparent 1px),
    radial-gradient(150% 115% at 50% 50%, var(--wall-glow-core) 0%, var(--wall-glow-mid) 46%, var(--wall-glow-edge) 100%);
  background-size:40px 40px, 40px 40px, 100% 100%;
  background-position:left top, left top, center center;
  background-repeat:repeat, repeat, no-repeat;
}
/* Held a little larger on a phone. The squares are read at arm's length there
   rather than across a desk, and below about 30px the hairlines start to sit
   close enough to shimmer against the pixel grid as the wall moves. */
@media (max-width:700px){
  .wall-page{background-size:34px 34px, 34px 34px, 100% 100%}
}
.wall-card:hover .more-link,.wall-card:focus-visible .more-link{color:var(--ink)}
.wall-card:hover .more-link svg{transform:translateX(3px)}
html[lang="ar"] .wall-card:hover .more-link svg{transform:scaleX(-1) translateX(3px)}

/* The Selected Work card is not a link: only its "See all work" corner goes
   to /work, and that lights up on its own hover through .more-link. */
.work-card .card-head{width:100%}

/* ---- 5 · process ---- */
.steps{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:clamp(18px,2.4vw,34px);
}
.steps li{display:flex;flex-direction:column;gap:5px;min-width:0}
.step-n{
  font-family:var(--font-display);font-style:normal;font-size:.82rem;font-weight:700;
  letter-spacing:.14em;color:var(--ink-dim);opacity:.6;margin-bottom:4px;
}
.steps li b{font-family:var(--font-display);font-size:clamp(1.05rem,1.4vw,1.3rem);font-weight:600;color:var(--ink)}
.steps li em{font-style:normal;font-size:clamp(.85rem,.95vw,.96rem);line-height:1.55;color:var(--ink-dim)}

/* ---- 7 · kind words ---- */
.quotes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:clamp(18px,2.2vw,30px)}
.quote{margin:0;display:flex;flex-direction:column;gap:14px;min-width:0}
.quote blockquote{
  margin:0;font-size:clamp(.98rem,1.15vw,1.1rem);line-height:1.6;color:var(--ink);
  /* the mark is set in the margin so the first line of the quote still starts
     on the column the rest of the card does */
  position:relative;padding-top:26px;
}
.quote blockquote::before{
  content:"\201C";position:absolute;top:-6px;inset-inline-start:0;
  font-family:var(--font-display);font-size:3rem;line-height:1;
  color:var(--ink);opacity:.22;
}
.quote figcaption{display:flex;flex-direction:column;gap:1px;margin-top:auto}
.quote figcaption b{font-size:.92rem;font-weight:700;color:var(--ink)}
.quote figcaption em{font-style:normal;font-size:.8rem;color:var(--ink-dim)}

/* ---- 8 · playground ---- */
.play{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}
.play li{display:flex;align-items:center;gap:14px;min-width:0}
.play-shot{
  flex:none;width:clamp(48px,5vw,64px);aspect-ratio:1;border-radius:14px;
  border:1px solid var(--line);
  background:linear-gradient(150deg,var(--card-2),var(--card));
}
.play li b{display:block;font-size:clamp(.95rem,1.05vw,1.06rem);font-weight:700;color:var(--ink);line-height:1.3}
.play li em{display:block;font-style:normal;font-size:.8rem;color:var(--ink-dim)}

/* ---- 9 · timeline ---- */
.tl{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.tl li{
  display:flex;gap:16px;align-items:baseline;min-width:0;
  padding-block:clamp(9px,1.1vw,14px);border-top:1px solid var(--line);
}
.tl li:first-child{border-top:0;padding-top:0}
.tl li:last-child{padding-bottom:0}
.tl li i{
  flex:none;font-style:normal;font-family:var(--font-display);
  font-size:.86rem;font-weight:700;letter-spacing:.06em;color:var(--ink-dim);opacity:.75;
  min-width:4ch;
}
.tl li span{font-size:clamp(.9rem,1vw,1rem);line-height:1.5;color:var(--ink)}

/* ---- 10 · right now ----
   The dot is the whole status: a live thing on a page that is otherwise still.
   It sits inside the label, so it reads as part of the word rather than as a
   badge stuck next to it. */
.now-card > .label{display:flex;align-items:center;gap:10px}
.now-dot{
  width:8px;height:8px;border-radius:50%;flex:none;
  background:var(--a1,#4ade80);
  box-shadow:0 0 0 0 color-mix(in srgb,var(--a1,#4ade80) 55%,transparent);
  animation:now-pulse 2.4s var(--ease) infinite;
}
@keyframes now-pulse{
  70%{box-shadow:0 0 0 9px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}
@media (prefers-reduced-motion:reduce){.now-dot{animation:none}}

/* ---- 11 · faq ---- */
.faq{display:flex;flex-direction:column}
.faq details{border-top:1px solid var(--line)}
.faq details:first-child{border-top:0}
.faq summary{
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding-block:clamp(13px,1.5vw,18px);cursor:pointer;list-style:none;
  font-size:clamp(.98rem,1.15vw,1.12rem);font-weight:600;color:var(--ink);
  transition:opacity .25s var(--ease);
}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{opacity:.72}
.faq summary:focus-visible{outline:2px solid var(--a2);outline-offset:3px;border-radius:6px}
/* the marker is drawn rather than a glyph, so it can turn */
.faq summary i{
  flex:none;position:relative;width:13px;height:13px;
  transition:transform .3s var(--ease);
}
.faq summary i::before,.faq summary i::after{
  content:"";position:absolute;inset-inline:0;top:50%;height:1.5px;
  background:var(--ink);border-radius:2px;
}
.faq summary i::after{transform:rotate(90deg);transition:transform .3s var(--ease)}
.faq details[open] summary i::after{transform:rotate(0deg)}
.faq details p{
  margin:0;padding-bottom:clamp(14px,1.6vw,20px);
  max-width:62ch;font-size:clamp(.9rem,1vw,1rem);line-height:1.65;color:var(--ink-dim);
}
@media (prefers-reduced-motion:reduce){.faq summary i,.faq summary i::after{transition:none}}

/* ---- 12 · the close ----
   No card. The page has been a column of panes for eleven sections, and the
   way out is the one thing that should not look like another one of them. */
.close{
  display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:clamp(18px,2.4vw,30px);
  padding-block:clamp(72px,10vw,150px) clamp(20px,3vw,44px);
}
.close .env{
  position:relative;width:clamp(120px,15vw,180px);
  padding:0;border:0;background:none;cursor:pointer;display:block;
  color:var(--ink);
}
.close h2{
  font-size:clamp(1.9rem,5.5vw,3.6rem);font-weight:600;
  letter-spacing:-.03em;line-height:1.05;color:var(--ink);max-width:16ch;
}
/* .cta-btn pins itself to the start edge of whatever card it is in — here
   there is no card, and the button belongs under the middle of the question
   rather than under its first word. */
.close .cta-btn{margin-top:0;align-self:center}
html[lang="ar"] .close h2,html[lang="ar"] .close .cta-btn{direction:rtl}

@media (max-width:760px){
  .steps{grid-template-columns:1fr}
  .quotes{grid-template-columns:1fr}
}

/* ================= /ABOUT — THE PASS =================
   One screen, three columns, no scroll. The page is the height of the window
   and nothing runs past it: what would not fit is not on the page rather than
   folded away behind a toggle. The middle column holds the pass itself, which
   hangs from a strap and swings — see src/components/about/Pass.tsx for the
   springs; everything below only composes what that writes. */

.pass-stage{
  /* Wider than the site's own measure on purpose. Everywhere else 1340px is
     the line a paragraph stops being readable past; this page has no
     paragraph running its width — it is three panels side by side, and the
     cap was leaving a hand's width of empty page outside the two outer ones
     on any screen bigger than a laptop. So the stage runs to the gutter and
     only stops on a monitor wide enough that the outer cards would start to
     sprawl. */
  /* Set to the same line as the page under it. It used to run to 1800px on a
     margin of its own — a gutter and then some — while the Selected Work card
     three hundred pixels below stopped at the site's own measure, and the two
     edges missing each other by a hand's width is the first thing you see
     scrolling off the cover. So the stage is the shell: the same width, the
     same gutter, and the cards line up with everything below them. */
  width:min(calc(var(--page) + var(--gutter) * 2),100%);margin-inline:auto;
  /* Wider margins than the site's own. Now that the stage runs to the gutter
     rather than stopping at a measure, the site's --gutter is the only thing
     between the outer cards and the edge of the screen — and one gutter, which
     is right for a column of text, reads as no margin at all under a band of
     glass panels. So this page pays a second one on top of it. */
  /* Centred under the header, not in the window.
     The nav is fixed and 49px tall, so it is not part of the box the cards are
     laid out in — an equal padding top and bottom would put the band visibly
     low, because the bar's 49px comes out of the top and nothing gives it
     back. So the top is written as what it is — the bar, plus the margin —
     and the same margin is paid at the foot. The band then carries the same
     air above it as below it, and reads as one object hung under the header
     rather than one that drifted down the window.

     It used to be a generous band at the bottom, on the argument that it let
     the type breathe; the cards are what this page is, and the empty page
     under them was height the cards could have had. */
  /* 26 above, 60 below — set by eye rather than matched by arithmetic. The
     top is written as what it is: the bar's 49px, plus the air under it. The
     cards used to hang off the bar on a hairline, which read as stuck to it;
     they now clear it, and the deeper band still goes at the foot, where the
     stage ends. The stage is exactly one window tall and the footer follows
     it with no margin, so that band is both — the last of the screen, and the
     distance to the footer's edge. The side margins are wider than the
     gutter's own second helping for the same reason the band is deep: the
     cards read as one object set into the page, not as a page they fill. */
  padding:calc(49px + 26px) var(--gutter) 60px;
  display:grid;
  /* The pass is the subject, and it is the one thing here with a size of its
     own: a card on a strap, drawn at the size a card is. So the middle track
     is a width rather than a share — the 550px it settled at when the three
     were shares of 1340 — and the extra room the wider stage brings goes to
     the two columns either side, which are lists and grow gracefully.

     The percentage is the same width expressed the other way, for the range
     between the stacking point and where 550px is reached: it tracks what the
     old .94/1.52/.94 gave to within a pixel, so nothing moves on a laptop. */
  /* Two tracks, not three. The pass was in the middle, between the greeting
     and the list of what I do, which made a wall of the one object on the page
     that is meant to hang free. It is on the right edge now, at the size a
     card is — a width rather than a share — and everything said about me has
     the rest of the line in one piece. */
  grid-template-columns:minmax(0,1fr) min(580px,47%);
  gap:clamp(10px,.9vw,16px);
  position:relative;z-index:1;
  /* the grid order is the layout, and this site never mirrors — only the text
     inside each card turns around, which .card already does for itself */
  direction:ltr;
}
/* Except here. On every other page the columns are furniture and the text is
   the only thing with a reading order; on this one the three columns ARE the
   order — the greeting, then the pass, then what I do — and read from the left
   in Arabic that introduction runs backwards. So the stage turns with the
   language: the card that says who this is leads on the right edge, the pass
   stays in the middle, and what I do finishes on the left. */
html[lang="ar"] .pass-stage{direction:rtl}
/* Held to the window only where a window that shape can actually hold it.
   Narrower or shorter than this and the three columns become one, the page
   scrolls, and nothing has to be squeezed.

   Held to it, not locked to it: the scroll used to be nailed shut here, and
   with the footer under the stage that would have hidden it for good. The
   stage is still exactly one window tall, so the screen the reader lands on
   is the same screen it always was — there is simply something below it now
   for anyone who goes looking. */
@media (min-width:1080px) and (min-height:620px){
  .pass-stage{height:100dvh}
}

/* The stage already ends in a generous band of its own padding, so the footer
   does not need the 60px it takes everywhere else — it would read as a gap
   rather than as the page ending. */
/* Only when the cover IS the whole page. With the work and the counts under
   it the footer follows those instead, on the site's own spacing. */
.pass-stage + .site-footer{margin-top:0}

.pass-col{display:grid;gap:clamp(10px,.9vw,16px);min-height:0;min-width:0}
/* The two columns are named rather than counted. They used to be :first-child
   and :last-child, which stopped being true the moment the scroll hint was
   added at the foot of the stage — it became the last child, and the right
   column silently lost its template. */
/* left: the tall card takes the slack, and everything under it — the location
   block, the buttons — sits at its own height. One 1fr track plus implicit
   auto rows, so a card can be added under it without the template being told. */
.pass-stage>.pass-left{grid-template-rows:minmax(0,1fr) auto;grid-auto-rows:auto}

/* ---- the floor of that column: the count and the tools, side by side ----
   They were a stack down a column of their own on the far side of the page,
   each as wide as that column and neither of them needing it — two figures and
   one row of icons. Under the greeting they are a footnote to it, at a
   footnote's height, and the greeting takes everything above them.

   Two equal halves. The strip of marks had the wider track, on the argument
   that a row of icons needs the room a pair of figures does not — but two
   cards of different widths under one card the width of both reads as a
   mistake in the grid rather than as a decision about icons. Even halves, and
   the marks size themselves to what they are given. */
.pass-under{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(10px,.9vw,16px);min-width:0;
}

/* The cards on this page are a screenful of panels rather than a page of
   sections, so they are tighter than the site's own and they carry a hairline
   — with five of them abutting, the edge is what tells one from the next. */
.pass-stage .card{
  padding:clamp(15px,1.4vw,23px);
  gap:clamp(7px,.7vw,13px);
  border-radius:clamp(18px,1.6vw,26px);
  border-color:var(--line);
  min-height:0;min-width:0;overflow:hidden;
}
/* nothing lifts here: on a page that does not scroll, a hover that moves the
   furniture reads as a wobble rather than as a response */
.pass-stage .card:hover{transform:none}
/* the site's card title is set for a page you scroll through; here it labels a
   panel a few centimetres tall */
.pass-stage .card .label{font-size:clamp(.88rem,1.02vw,1.08rem);letter-spacing:.16em}
/* and the link around a title carries that size too, so the arrow measured in
   em is measured in the title's em */
.pass-stage .card .head-go{font-size:clamp(.88rem,1.02vw,1.08rem)}

/* ---- left, top: who is on the pass ----
   The greeting leads and the caption sits directly under it rather than being
   pushed to the far end of the card — a name and the line that explains it are
   one thing, and holding them apart with the height of the card read as a
   missing paragraph.

   Two auto margins do the vertical, not justify-content: the free space in the
   card splits in half between them, which lands the greeting-and-paragraph
   block a little above the middle and the row of marks flat on the bottom
   edge. */
/* The block reads a little high in the card rather than dead centre: a
   greeting that starts halfway down looks like it is waiting for something
   above it. Not at the top either — the room above the word is what makes it
   a greeting rather than a heading — so the card carries extra padding at the
   foot and the group centres inside what is left. */
.pass-stage .pass-id{gap:clamp(22px,3.4vh,44px);justify-content:center}

/* ---- the signature, across the corner of the pass's panel ----
   It was written on the greeting's card, then off that card's corner. It is on
   the ground the pass hangs against now: bottom-right of that panel, over the
   glass and past its edge, and UNDER the pass. Pass.tsx renders it between the
   dotted ground (z 0) and the pivot (z 2), so z 1 puts it in front of the one
   and behind the other — pull the card down over the corner and the card
   covers the name.

   The box is the height the mark actually occupies once it leans — a
   signature 732 by 354 turned twenty degrees stands about four fifths of its
   width tall — and the writing is centred in it.

   The layer of its own is not decoration. The stage above carries a tilt, and
   inside a 3D rendering context Chromium rasterises the subtree once and reuses
   the texture — an animation whose frames advance while the screen does not.
   See the note over .sig further up, and Signature.tsx. */
.pass-scene .sig{
  position:absolute;z-index:1;
  /* Stacked, the page is a column the width of the window, so the mark stays
     inside the panel's corner: past the edge there is only the gutter, and on
     a phone that is a few pixels. */
  --sig:clamp(170px,46vw,300px);
  width:var(--sig);aspect-ratio:1 / .8;
  inset:auto;right:0;bottom:0;
  margin:0;transform:none;
  pointer-events:none;
}
.pass-scene .sig .sig-line{
  position:absolute;top:50%;left:50%;width:100%;
  transform:translate(-50%,-50%) rotate(-20deg) translateZ(0);
  will-change:transform;
}
/* Arabic: the stage turns, so the pass's panel is on the left edge and the
   name goes off that panel's outer corner, leaning the other way. */
html[lang="ar"] .pass-scene .sig{inset:auto;left:0;bottom:0;transform:none}
html[lang="ar"] .pass-scene .sig .sig-line{
  transform:translate(-50%,-50%) rotate(20deg) translateZ(0);
}
.pass-hello{
  display:grid;
  font-family:var(--font-display);
  font-size:clamp(2.7rem,4.8vw,4.9rem);
  font-weight:800;letter-spacing:-.035em;line-height:1.02;
}
html[lang="ar"] .pass-hello{letter-spacing:0;line-height:1.25}
/* A caption, not a second paragraph: smaller than the body copy elsewhere on
   the page, because what it is doing is explaining the name above it. */
.pass-blurb{display:grid;gap:clamp(14px,1.8vh,22px)}
.pass-blurb p{font-size:clamp(.95rem,1.08vw,1.14rem);line-height:1.6;color:var(--ink-dim);
  max-width:34ch}
/* ---- the marks, under the caption ----
   Five ways to reach me, set as a row of bare marks rather than a grid of
   panels. A panel each was a block of furniture the size of the paragraph
   above it; the marks alone are the same five destinations at a tenth of the
   weight, and they finish the caption rather than interrupting it.

   They sit under the paragraph, not on the card's bottom edge: the row lives
   inside .pass-blurb now, so it takes that block's own gap and stays with the
   words it follows however tall the card grows. The slack in the card collects
   below the block instead — see .pass-blurb's margin-bottom.

   Size and spacing are the footer's, to the pixel. It is the same five marks
   in both places and there is no reason for them to be two different rows. */
.pass-marks{display:flex;align-items:center;gap:17px}
html[lang="ar"] .pass-marks{direction:rtl}
/* No box around them. A panel each was five more edges on a card that already
   has one, and on ground this blue the boxes read as holes in it — the marks
   alone are the same five destinations with nothing drawn around them. */
.pass-marks .mark{
  flex:none;display:grid;place-items:center;
  width:32px;height:32px;
  color:#b3b3b3;text-decoration:none;
  border:0;background:none;
  transition:color .24s cubic-bezier(.4,0,.2,1);
}
.pass-marks .mark:hover,.pass-marks .mark:focus-visible{color:var(--ink)}
/* Hover is brightness and nothing else — no lift, no growth. The mark stays
   exactly where it is and exactly the size it is, and only comes up out of the
   ground it sits in. Same in the footer. */
.pass-marks .mark svg{width:100%;height:100%;fill:currentColor}
.pass-marks .mark:focus-visible{outline:2px solid var(--a2);outline-offset:4px;border-radius:4px}

/* ---- and the résumé, one line under the marks ----
   It had a card of its own in the corner of the column: a whole panel of glass
   carrying one sentence and an arrow, which is a lot of card for a link. The
   marks above are already the row of places this page sends you — the record
   is one more, and the one worth a word rather than an icon, so it sits with
   them as the site's own pill rather than as a panel.

   Its own row, not the marks' row. Set beside four icons it read as a fifth
   destination of the same weight; under them it reads as the one you take if
   the icons were not what you came for. */
.pass-resume{display:flex}
html[lang="ar"] .pass-resume{direction:rtl}
.pass-resume .about-btn{height:38px;padding:0 18px;font-size:.78rem;letter-spacing:.02em}
.pass-resume .about-btn svg{width:14px;height:14px;transition:transform .3s var(--ease)}
html[lang="ar"] .pass-resume .about-btn svg{transform:scaleX(-1)}
.pass-resume .about-btn:hover svg{transform:translate(2px,-2px)}
html[lang="ar"] .pass-resume .about-btn:hover svg{transform:scaleX(-1) translate(2px,-2px)}
@media (prefers-reduced-motion:reduce){
  .pass-resume .about-btn:hover svg{transform:none}
  html[lang="ar"] .pass-resume .about-btn:hover svg{transform:scaleX(-1)}
}

/* ---- left, middle: where I am, and what time it is there ---- */
.pass-stage .pass-where{position:relative;justify-content:flex-end;gap:4px}
.pass-where>b{font-size:1.12rem;font-weight:700;color:var(--ink)}
/* A map without a map: a few streets at two angles, rings around where I am,
   and a lit dot in the middle of them — enough to read as a place, and no tiles
   to fetch from anyone. Fades out to the left so the text sits on clean ground. */
.pass-map{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:
    radial-gradient(circle at 76% 30%, rgba(255,255,255,.95) 0 2.2px, transparent 3px),
    radial-gradient(circle at 76% 30%, transparent 0 15px, rgba(255,255,255,.16) 15px 16px, transparent 17px),
    radial-gradient(circle at 76% 30%, transparent 0 30px, rgba(255,255,255,.1) 30px 31px, transparent 32px),
    repeating-linear-gradient(62deg, transparent 0 23px, rgba(255,255,255,.055) 23px 24px),
    repeating-linear-gradient(-26deg, transparent 0 31px, rgba(255,255,255,.05) 31px 32px);
  opacity:.85;
  -webkit-mask-image:linear-gradient(to left,#000 10%,transparent 82%);
  mask-image:linear-gradient(to left,#000 10%,transparent 82%);
}
/* Turned around with the column it is in: the text now sits on the right of
   this card, so the streets have to thin out that way instead. One mirror
   does it — the drawing has no type in it to come out backwards. */
html[lang="ar"] .pass-map{transform:scaleX(-1)}

/* ---- left, bottom: the résumé ----
   Was a card here; is a button under the marks now. See .pass-resume. */


/* ---- left, bottom: the two ways on ---- */
/* One row, never two: the CV is the short word beside Contact me, and wrapped
   under it it read as a second call to action rather than a footnote to one. */
.pass-acts{margin-top:0;gap:8px;flex-wrap:nowrap}
html[lang="ar"] .pass-acts{direction:rtl}
/* They are not equals. Contact me is the thing this page is for, so it takes
   the column; the CV sits beside it at the width of the word on it.

   Both buttons are DIRECT children here — CvRequest returns its trigger from a
   fragment, so there is no wrapper to size, and a width:100% on the pair blew
   the short one out to the width of the row. Flex does the sizing. */
.pass-acts .about-btn{width:auto;height:52px;padding:0 clamp(14px,1.4vw,22px);
  font-size:.92rem;white-space:nowrap}
.pass-acts>.about-btn:first-child{flex:1 1 0;min-width:0}
.pass-acts>.about-btn:last-child{flex:0 0 auto;font-size:.86rem;padding:0 16px}

/* ---- right, top: where I am with it ----
   The one card on this page that is a sentence rather than a list, which is
   what stops the right-hand column reading as two of the same thing. */
.pass-stage .pass-now{justify-content:center;gap:clamp(9px,1.2vh,15px)}
.pass-now p{font-size:clamp(.82rem,.92vw,.98rem);line-height:1.6;color:var(--ink-dim);margin:0}

/* The same slot, holding the two figures instead of a sentence.
   `.stats` is written for /resume, where the row is pushed to the bottom of a
   tall card by `margin-top:auto`; here the card is short and centred, so the
   row sits with the label rather than away from it, and the tiles are set to
   the height there is instead of to the résumé's fixed padding. */
.pass-numbers .stats{margin-top:0;gap:clamp(8px,1.1vw,12px)}
/* Slimmer than it wants to be, on purpose. Two figures need the height of two
   figures and nothing more, and every pixel this card does not take is a pixel
   the work below it does — which is the card that matters. The plates there
   are sized by the room the column has left, so the tiles were coming out
   flat; the height comes off here and the plates come back to a square. */
/* Given its height back. It was squeezed to the bone to find room for the
   plates below it; with the stage running to the foot of the window there is
   height to go round, and two figures in a card the size of a postage stamp
   was never the look — it was a compromise. */
.pass-stage .pass-numbers{padding-block:clamp(16px,1.5vw,24px);gap:11px}
.pass-numbers .stat{padding:clamp(10px,1.5vh,18px) 12px}
/* The figure carries the card and the word underneath only names it, so the
   two are set as one object rather than as two lines: the number large and on
   a line-height of exactly itself, the word small and pushed right up under
   it. The air between them was the default leading of a 2rem numeral, which
   at this size is a gap you read as a space rather than as a pair. */
.pass-numbers .stat b{font-size:clamp(1.75rem,2.1vw,2.3rem);line-height:1}
.pass-numbers .stat span{
  display:block;font-size:clamp(.55rem,.62vw,.66rem);
  line-height:1.2;margin-top:3px;letter-spacing:.12em;
}

/* ---- the work ----
   It was a card here and again in the home page's own Selected Work three
   hundred pixels below: the same four folders twice on one scroll. The card
   below keeps it, having the room to show it. */

/* ---- what I do — off the cover, kept for anywhere it comes back ---- */
/* Scrolls rather than clips: on a short window the last line was simply gone,
   and a list that ends mid-word is worse than one you nudge. The page itself
   still does not move — only this. */
/* The list is the whole of a full-height column now that the marks have left
   it, and five lines bunched at the top of it left the card looking like it
   had lost something. So the five rows share the column equally and a hairline
   sits between them: an index rather than a stack, and it fills the card
   honestly instead of being spaced out to look like it does. Where the list is
   taller than the card the tracks go back to their own height and the scroll
   below takes over. */
.does{list-style:none;display:grid;flex:1 1 auto;align-content:stretch;gap:0;
  min-height:0;overflow-y:auto;overscroll-behavior:contain;
  /* room for the bar, so it never sits on top of the words it is scrolling */
  padding-inline-end:9px;
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.15) transparent}
.does::-webkit-scrollbar{width:4px}
.does::-webkit-scrollbar-track{background:transparent}
.does::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:2px}
.does li{display:flex;gap:13px;align-items:center;min-width:0;
  padding-block:clamp(5px,.8vh,10px);border-top:1px solid var(--line)}
.does li:first-child{border-top:0;padding-top:0}
.does li:last-child{padding-bottom:0}
.does-n{
  flex:none;font-family:var(--font-num);font-style:normal;
  font-size:.84rem;font-weight:700;color:var(--ink-faint);line-height:1.5;
}
.does li>span{min-width:0}
.does li b{display:block;font-size:clamp(.96rem,1.05vw,1.1rem);font-weight:700;color:var(--ink);line-height:1.3}
.does li em{display:block;font-style:normal;font-size:clamp(.78rem,.84vw,.88rem);color:var(--ink-dim);
  line-height:1.4;margin-top:2px}

/* ---- right, foot: the apps ----
   One row, its own height, and it never wraps: on this page the strip is
   always the marquee, so a second row would only ever be a mis-measure. The
   icons are a notch under their site size — 56px squircles in a panel this
   tall would read as the subject rather than as the footnote it is. */
/* Education reads like a shorter .does: same hairline between rows, but the
   years sit under the line rather than beside a number. */
.edu{list-style:none;display:flex;flex-direction:column;gap:0;min-width:0}
.edu li{display:flex;flex-direction:column;gap:1px;
  padding-block:clamp(5px,.8vh,10px);border-top:1px solid var(--line)}
.edu li:first-child{border-top:0;padding-top:0}
.edu li:last-child{padding-bottom:0}
.edu li b{font-size:clamp(.96rem,1.05vw,1.1rem);font-weight:700;color:var(--ink);line-height:1.3}
.edu li em{font-style:normal;font-size:clamp(.78rem,.84vw,.88rem);color:var(--ink-dim)}
.edu li i{font-style:normal;font-size:.74rem;letter-spacing:.06em;color:var(--ink-dim);opacity:.7}

/* The apps at a size you can actually tell apart. They were a notch under the
   site's own, on the reasoning that a list of tools is a footnote — but a
   footnote you have to lean in to read is not doing its job either, and the
   marks are the whole content of this card. The card grows with them and takes
   the height out of the work above it, which had the slack to give. */
/* Tighter than the card wants to be, for the same reason the figures above it
   are: this is a line in a dossier — one row of marks saying what I make it in
   — and the work between them is the card the page is for. The room it does
   not take is the room the plates need to come back to a square. */
.pass-stage .pass-apps{gap:10px;padding-block:clamp(12px,1.1vw,17px)}
/* No title over the switch. "Tools I live in" and a switch reading APPS / CODE
   said the same thing twice on one line, and the switch says it in the words
   that are also the control. So the switch is the title: centred on the head's
   line, alone on it, with the strip under it. */
.pass-apps .card-head{justify-content:center}
.pass-apps .seg{align-self:center}
.pass-apps .app-strip{flex-wrap:nowrap;margin-top:0;min-width:0}
.pass-apps .app-icon{width:clamp(50px,4.4vw,64px);height:clamp(50px,4.4vw,64px)}

/* ================= THE ONE SCREEN, AS THE WINDOW SHORTENS =================
   Everything below only applies where the page is actually locked to the
   window — the same test the lock itself is written with, so a stacked phone
   never sees a line of it.

   The rule the page used to follow was that the work card took whatever the
   column had left over. Drag the bottom of the window up and that card paid
   for all of it: the folders flattened into letterboxes and the paper in them
   shrank to a stamp, while the two small cards either side of it kept every
   pixel they had and the pass in the middle sat in a field of empty glass.

   So the order of who gives is turned round. The folders are the subject of
   the card and they hold their shape and their share; the count above them and
   the tools below give their height first, at the rate the window loses it,
   and the pass in the middle — the one thing on the page built to be any size
   — gives the rest. Nothing here is a jump: every number is a share of the
   window's own height, so the page moves with the drag rather than at it. */
@media (min-width:1080px) and (min-height:620px){

  /* ---- the greeting, and the name under it ----
     The words sit a little above the middle of the card and the signature at
     the foot of it. They used to be one stack centred as a block, which put
     the greeting far higher than the middle: the signature is a tall object,
     and centring the pair lifts the words by half its height. Separated, the
     words take a band off the top of the card and start there, the slack
     collects between the two, and the name is pushed down onto the floor.

     The band is a share of the window, not a number, so the greeting keeps its
     place in the card at every height the card has. */
  /* Centred, not started from the top. The band above the greeting was a
     head start paid out of the card's own slack, which stood the whole group
     — greeting, caption, marks, name — high on the glass with the room left
     over sitting under the signature. The group takes the middle of the card
     instead and the slack splits evenly above and below it. */
  .pass-stage .pass-id{
    justify-content:center;
    gap:clamp(14px,2.6vh,40px);
    padding-block:clamp(15px,1.4vw,23px);
  }
  /* Two auto margins, and the slack in the card splits evenly between them:
     one above the greeting, one above the name. Which lands the words a
     little above the middle of the card and the signature on its floor —
     rather than the whole stack centred as a block, which put the greeting a
     third of the way up because the signature is tall and half of its height
     came off the top of the words. */
  /* ---- and on a window this shape the name hangs off the panel ----
     One screen, the panel has a gutter beside it and the stage's deep band
     under it, so the mark is bigger and goes past both edges: over the glass,
     and a little out into the page. The overhang is a fraction of the mark so
     it is the same gesture at every size, and never more than the gutter, so
     the last stroke stops short of the window. */
  .pass-scene .sig{
    --sig:clamp(240px,min(30vw,36svh),520px);
    /* The room beside the panel: the stage's gutter, plus whatever the
       window has past the stage's own width. The lean pushes the ink about
       six hundredths of the mark past its box, and that comes off the room
       first so the tail never reaches the window's edge. */
    --room:calc(var(--gutter) + max(0px,(100vw - var(--page) - 2 * var(--gutter)) / 2));
    --hang:max(0px, min(.36 * var(--sig), var(--room) - .06 * var(--sig) - 20px));
    bottom:calc(-.22 * var(--sig));
    right:calc(-1 * var(--hang));
  }
  html[lang="ar"] .pass-scene .sig{
    right:auto;
    bottom:calc(-.22 * var(--sig));
    left:calc(-1 * var(--hang));
  }
  /* Two lines set this big are the tallest thing on the card, so the size
     answers to whichever is scarcer — the width to fit across, or the height
     to fit into. On an ordinary window the width still decides, exactly as
     it did. */
  .pass-hello{font-size:clamp(2.3rem,min(4.8vw,7.1vh),4.9rem)}
  /* and the caption with it, for the same reason */
  .pass-blurb{gap:clamp(10px,1.8vh,22px)}
  .pass-blurb p{font-size:clamp(.86rem,min(1.08vw,1.75vh),1.14rem)}


  /* ---- and the two cards on the floor give first ----
     Both were written to the width of the page, which on a window losing
     height means they do not move at all and the greeting above them pays the
     whole bill. Written to the height as well, they hand back a little at a
     time — which is the room the greeting keeps. */
  .pass-stage .pass-numbers{padding-block:clamp(9px,min(1.5vw,2.1vh),24px)}
  .pass-numbers .stat{padding:clamp(6px,1.3vh,18px) 12px}
  .pass-numbers .stat b{font-size:clamp(1.35rem,min(2.1vw,3.4vh),2.3rem)}
  .pass-stage .pass-apps{padding-block:clamp(7px,min(1.1vw,1.6vh),17px)}
  .pass-apps .app-icon{
    width:clamp(40px,min(4.4vw,7.1vh),64px);
    height:clamp(40px,min(4.4vw,7.1vh),64px);
  }
  .pass-stage .card .label{font-size:clamp(.78rem,min(1.02vw,1.6vh),1.08rem)}
  .pass-stage .card .head-go{font-size:clamp(.78rem,min(1.02vw,1.6vh),1.08rem)}
}

/* ================= THE PASS ITSELF ================= */

/* The one panel that does not clip its contents. A pass swinging inside a box
   is a picture of a pass; one that swings out over the panel next to it, with
   its shadow falling across it, is an object hanging in front of the page. The
   ground it hangs against still has to stop at the rounded corner, so the clip
   moves down onto .pass-grid, which is the only thing that needed it. */
.pass-stage .pass-hang{padding:0;gap:0;overflow:visible;z-index:2}
.pass-scene{
  position:relative;flex:1 1 auto;width:100%;min-height:0;
  border-radius:inherit;overflow:visible;
  /* A toy, not text. Dragging the pass was starting a selection that swept
     over the signature and painted it browser-blue. Nothing in here is worth
     copying, so nothing in here can be selected or dragged out as an image. */
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
  /* every size inside the scene is a fraction of the scene, so the pass keeps
     its proportions at any window height without a single media query */
  container-type:size;
  /* How far the strap runs, measured from the top edge of the scene. Not a
     flat fraction of the height: the card is sized off whichever of the
     scene's two dimensions is smaller, so on a tall narrow scene a fixed strap
     left a third of the panel empty under the pass, and on a short wide one it
     pushed the card down onto the caption. A share of whatever room the card
     has NOT taken hangs it in the middle of the space it is actually in.

     At rest it is short, and it is meant to be: the length is in the cord's
     give, not in how it hangs. Take hold of the card and the strap pays out
     under the pull — see PULL_MAX in Pass.tsx — so the reach is something the
     hand finds rather than something the page starts with. */
  --strap:max(9cqh, (100cqh - min(70cqh,62cqw)) * .42);
}

/* The ground the pass hangs against: a dot field, and nothing else. No wash,
   no colour — every other panel on this page is lit, and the one holding the
   subject is the one that should not be. A plain dark field is what a lit
   object needs behind it. */
.pass-grid{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  /* carries the corner now that the scene no longer clips */
  border-radius:inherit;overflow:hidden;
  /* Far apart and barely there. At 27px and .22 the field read as a texture
     with a weave of its own, and the one panel on this page that is meant to
     be plain ground was competing with the object hanging in front of it.
     Half as many dots at half the strength is a field you notice only when
     you look for it, which is all it was ever for. */
  background-image:radial-gradient(rgba(255,255,255,.11) 1px, transparent 1.5px);
  background-size:44px 44px;
  background-position:center top;
}

/* A zero-size box at the hook. It never moves, so the anchor can be measured
   from it without asking any of the swinging parts where they are.

   It sits ON the top edge of the scene, not a little below it: the strap turns
   about this point, and a hook set any lower swings the top of the strap clear
   of the frame and leaves it hanging off nothing. On the edge, the frame
   itself is what the strap disappears into at every angle. */
.pass-pivot{position:absolute;left:50%;top:0;width:0;height:0;z-index:2}

/* the strap, and everything hanging off it, turning about the hook */
.pass-swing{
  position:absolute;left:0;top:0;width:0;
  /* --pull is the give in the cord: 1 at rest, a little over it while the card
     is being held away from under the hook. Everything on the strap — the clip
     and the card included — is positioned off this height, so lengthening it
     is the whole of the effect. */
  height:calc(var(--strap) * var(--pull,1));
  transform-origin:50% 0;
  transform:rotate(var(--theta,0deg));
  will-change:transform;
}
/* ---- the strap ----
   A flat strip of webbing, not a cable. The shading that used to run from
   black at both edges up to a crest in the middle turned the strap into a
   tube — a rope hanging off the frame. Webbing is a ribbon: it takes the
   light evenly across its face, and what tells you where its edges are is
   the hem, not a rolled-off highlight. So the fill is flat, the edges are
   drawn as edges, and only the twill and the stitching are left to say it
   is cloth.

   the twill    two sets of threads crossing at opposite diagonals, one catching
                the light and one in shadow. Rungs straight across read as a
                ladder; a diagonal weave reads as cloth.
   the stitch   the two lines of thread that hold the folded edges down, inset
                from the edge the way a real hem is.
   the crimp    the metal barrel at the bottom that pinches the cloth into the
                clip. It is the join a lanyard actually has, and without it the
                strap simply stopped.

   And it does not begin: the top few percent fade out, so the strap arrives
   from somewhere above the frame instead of being cut off square against it. */
.pass-strap{
  position:absolute;left:50%;top:0;translate:-50% 0;
  width:clamp(17px,3.1cqh,27px);height:100%;
  background-image:
    repeating-linear-gradient(56deg, rgba(255,255,255,.07) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(-56deg, rgba(0,0,0,.38) 0 1px, transparent 1px 3px),
    linear-gradient(90deg,
      transparent 0 13%, rgba(255,255,255,.13) 13% 15%,
      transparent 15% 85%, rgba(255,255,255,.13) 85% 87%, transparent 87%),
    /* flat face, with the two hem edges darkened by a hair — enough to read as
       a folded edge, not enough to round the strip off */
    linear-gradient(90deg,#242424 0 6%,#333 6% 94%,#242424 94%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05), 2px 0 8px -4px rgba(0,0,0,.9);
  /* the fade at the top, so the strap runs up out of the frame rather than
     stopping at it. The crimp and the clip are separate elements, so masking
     the strap does not touch them. */
  -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 clamp(9px,4cqh,34px));
  mask-image:linear-gradient(180deg, transparent 0, #000 clamp(9px,4cqh,34px));
  /* The twist. A flat ribbon turned away from you narrows to its own edge and
     loses the light it was taking; both are written by the loop, because both
     have to track a spring rather than a curve. Scale rather than rotateY:
     there is no perspective on the strap, so a rotation about the vertical
     would not foreshorten at all. */
  transform:scaleX(var(--strap-sx,1));
  filter:brightness(var(--strap-dim,1));
}
.pass-strap::after{
  content:"";position:absolute;left:50%;bottom:0;translate:-50% 0;
  width:calc(100% + clamp(3px,.6cqh,6px));height:clamp(7px,1.3cqh,12px);
  border-radius:2px;
  background:linear-gradient(90deg,#333,#c9c9c9 26%,#efefef 43%,#828282 70%,#2b2b2b);
  box-shadow:0 2px 4px rgba(0,0,0,.8), inset 0 1px 0 rgba(255,255,255,.75);
}
/* the clip on the end of it, in metal */
.pass-clip{
  position:absolute;left:50%;top:calc(100% - 1.4cqh);translate:-50% 0;
  width:clamp(19px,3.2cqh,26px);height:clamp(24px,4.2cqh,34px);
  border-radius:6px;
  background:linear-gradient(148deg,#f1f1f1,#8d8d8d 42%,#d6d6d6 64%,#5f5f5f);
  box-shadow:0 3px 8px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.85);
  z-index:3;
  /* Crimped onto the end of the webbing, so it turns with the webbing: the
     same narrowing, on the same frame. On the card's own turn instead, it ran
     ahead of the barrel it is joined to and the two read as separate objects.

     The strap's dimming is NOT taken with it. Webbing has a face and a back —
     one finished, one not — and darkens as it comes round. A metal clip is the
     same metal on both sides, so it stays the brightness it is; only its width
     changes. */
  transform:scaleX(var(--strap-sx,1));
}
.pass-clip::after{
  content:"";position:absolute;left:50%;top:26%;translate:-50% 0;
  width:36%;height:46%;border-radius:3px;background:#0a0a0a;
  box-shadow:inset 0 1px 2px rgba(0,0,0,.9);
}

/* ---- the card on the end ---- */
.pass-card{
  position:absolute;left:50%;top:100%;translate:-50% 0;
  /* Sized against the WIDTH of the scene as much as its height. The scene is a
     tall column, so a card measured only in cqh came out wide enough to touch
     both edges — and a pass with no room either side of it cannot swing
     without being sliced off by its own frame. */
  height:clamp(180px,min(70cqh,62cqw),440px);aspect-ratio:5/7;
  /* it turns about the clip, which is a little above its own top edge */
  transform-origin:50% -8px;
  /* --flip is the turn, and it is added to the yaw rather than layered over
     it: they are the same rotation about the same axis, and a card that turns
     on one element while it leans on another is two things pretending to be
     one. Composed here, a pass caught mid-swing turns from wherever it is. */
  transform:rotate(var(--phi,0deg))
            perspective(900px)
            rotateY(calc(var(--yaw,0deg) + var(--flip,0deg)))
            rotateX(var(--pitch,0deg));
  transform-style:preserve-3d;
  will-change:transform;
  cursor:grab;
  /* a drag on the card is a drag on the card, not a scroll of the page under it */
  touch-action:none;
  -webkit-user-select:none;user-select:none;
}
.pass-card.is-held{cursor:grabbing}

/* the sleeve: clear plastic with a punched slot, the way a real pass is made */
.pass-face{
  position:absolute;inset:0;border-radius:clamp(12px,1.9cqh,20px);
  padding:clamp(6px,.9cqh,10px);
  padding-top:clamp(16px,2.6cqh,26px);
  /* the barcode sits on this edge, and butted against the border it read as a
     printing error rather than as the bottom of a card */
  padding-bottom:clamp(9px,1.5cqh,15px);
  display:flex;flex-direction:column;
  background:
    linear-gradient(158deg, rgba(255,255,255,.16), rgba(255,255,255,.05) 42%, rgba(0,0,0,.3));
  border:1px solid rgba(255,255,255,.4);
  /* --cast slides the drop shadow the opposite way to the turn: the card has
     moved, the light above it has not. */
  box-shadow:
    var(--cast,0px) 34px 60px -30px rgba(0,0,0,.95),
    calc(var(--cast,0px) * .5) 10px 26px -14px rgba(0,0,0,.7),
    inset 0 1.5px 0 rgba(255,255,255,.6),
    inset 0 -1px 0 rgba(255,255,255,.14),
    inset 3px 0 16px -8px rgba(255,255,255,.35),
    inset -3px 0 16px -8px rgba(255,255,255,.35);
  backdrop-filter:blur(3px) saturate(170%);
  -webkit-backdrop-filter:blur(3px) saturate(170%);
  overflow:hidden;
}
/* Everything on the face is print on a photograph now, so it all rides above
   it — and it all carries a little shadow, because print on a picture that
   casts nothing is print floating over a picture. */
.pass-head,.pass-foot,.pass-meta,.pass-code{position:relative;z-index:1}
.pass-face>*{text-shadow:0 1px 4px rgba(0,0,0,.7)}
/* the name and the small print no longer sit at the two ends of a flex column
   with a photo between them, so this is what holds them apart */
.pass-foot{margin-top:auto}
.pass-hole{
  position:absolute;left:50%;top:clamp(6px,1cqh,11px);translate:-50% 0;z-index:1;
  width:32%;height:clamp(5px,.8cqh,8px);border-radius:99px;
  background:rgba(0,0,0,.78);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.95),0 1px 0 rgba(255,255,255,.24);
}
/* ---- the photo IS the card ----
   Not a window cut into a layout: the picture runs corner to corner, under the
   padding and behind everything else, and the mark, the name and the small
   print are printed on top of it. That is how a real pass is made — the photo
   is the card, and the print is what was put on the card. */
.pass-photo{
  position:absolute;inset:0;z-index:0;
  border-radius:inherit;overflow:hidden;
  /* Lit from the same corner as everything else on this page. Flat black, it
     read as a hole punched through the card rather than as the panel a photo
     has not been dropped into yet. */
  background:
    radial-gradient(78% 58% at 30% 12%, rgba(255,255,255,.09), transparent 70%),
    linear-gradient(155deg,#232323,#0b0b0b);
  display:grid;place-items:center;
}
/* Ground for the print. Dark across the top where the mark and the year sit,
   dark and deeper across the bottom where the name, the small print and the
   code are, and barely there through the middle — which is where a face is,
   and the one part of a photograph nothing should be laid over. */
.pass-photo::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(to bottom,
    rgba(0,0,0,.68) 0,
    rgba(0,0,0,.24) 17%,
    rgba(0,0,0,.06) 42%,
    rgba(0,0,0,.34) 66%,
    rgba(0,0,0,.78) 86%,
    rgba(0,0,0,.9) 100%);
}
/* Held back until it is known to have loaded — see the note in Pass.tsx. A
   missing file leaves the mark and the ghost, never a broken-image glyph. */
.pass-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .4s var(--ease)}
.pass-photo img.is-there{opacity:1}
/* and once there is a real photo, the placeholder under it goes */
.pass-photo:has(img.is-there) .pass-ghost{opacity:0}
/* what shows until the photo is dropped in — over the scrim now, so it is not
   read through the same darkening the print sits on */
.pass-ghost{position:relative;z-index:3;width:30%;height:auto;aspect-ratio:1;
  fill:none;stroke:rgba(255,255,255,.34);stroke-width:1.1}
/* ---- the header: who issued it ----
   The mark used to sit across the middle of the photo at 40% of the card, which
   read as a watermark on a picture rather than as the top of a pass. Down here
   at badge size, with the year opposite it, it is the thing that says a card
   was issued — and it leaves the photo to be a photo. */
.pass-head{
  flex:none;display:flex;align-items:center;justify-content:space-between;
  gap:8px;padding:0 clamp(2px,.4cqh,4px) clamp(5px,.9cqh,9px);
}
/* Small, and white whatever the file is drawn in — the mark is the issuer's
   stamp in the corner of a pass, not the thing the pass is about. */
.pass-mark{width:clamp(15px,3cqh,26px);opacity:.92;pointer-events:none;
  filter:brightness(0) invert(1) drop-shadow(0 1px 4px rgba(0,0,0,.7))}
.pass-head span{
  font-family:var(--font-num);
  font-size:clamp(.5rem,1.05cqh,.64rem);font-weight:700;
  letter-spacing:.18em;color:rgba(255,255,255,.82);line-height:1;
}

.pass-foot{flex:none;padding:clamp(5px,.9cqh,9px) 2px 1px;text-align:center;direction:ltr}
html[lang="ar"] .pass-foot{direction:rtl}
.pass-foot b{
  display:block;font-family:var(--font-display);
  font-size:clamp(.7rem,1.7cqh,.95rem);font-weight:700;letter-spacing:-.015em;
  color:var(--ink);line-height:1.2;
}
html[lang="ar"] .pass-foot b{font-family:"SF Pro AR",sans-serif;letter-spacing:0}
.pass-foot span{
  display:block;margin-top:2px;
  font-size:clamp(.54rem,1.15cqh,.7rem);color:rgba(255,255,255,.8);line-height:1.3;
}
/* ---- the small print, and the barcode under it ----
   The two things every real pass carries and no decorative card ever does:
   where it was issued, and a number nobody reads. */
.pass-meta{
  flex:none;display:flex;align-items:center;justify-content:space-between;
  gap:8px;margin-top:clamp(5px,.9cqh,9px);
  padding-top:clamp(4px,.8cqh,8px);border-top:1px solid rgba(255,255,255,.22);
  font-family:var(--font-num);
  font-size:clamp(.42rem,.92cqh,.56rem);font-weight:600;
  letter-spacing:.13em;color:rgba(255,255,255,.62);line-height:1;
}
html[lang="ar"] .pass-meta>span:first-child{
  font-family:"SF Pro AR",sans-serif;letter-spacing:0;font-size:clamp(.46rem,1cqh,.6rem);
}
/* Three periods that never line up, so the bars come out uneven the way a
   printed code is — one repeating gradient on its own only ever reads as a
   fence. */
.pass-code{
  flex:none;height:clamp(13px,2.6cqh,22px);margin-top:clamp(4px,.8cqh,8px);
  border-radius:2px;
  background-image:
    repeating-linear-gradient(90deg, rgba(255,255,255,.62) 0 2px, transparent 2px 5px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.55) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.5) 0 3px, transparent 3px 11px);
  /* frayed at both ends rather than butted against the padding */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  opacity:.75;
}
/* ================= the two faces, and the turn =================
   The card has a back now, and the two of them turn together on an inner
   element: the physics writes to .pass-card and knows nothing about which way
   round the thing is, so a card that is mid-swing goes on swinging while it
   turns. Nothing here is read by the script. */
/* Each face shows only from its own side — this is the whole of the illusion,
   and with the turn now on .pass-card it is the card itself that decides which
   of the two you are looking at. */
.pass-face{-webkit-backface-visibility:hidden;backface-visibility:hidden}

/* ---- the back: white stock, dark print ----
   The inverse of the front, and most of the reason turning it over is worth
   doing. A back that is the same dark glass as the front is not a back, it is
   the front again with different words on it. */
.pass-back{
  transform:rotateY(180deg);
  align-items:center;text-align:center;
  padding:clamp(16px,2.6cqh,26px) clamp(9px,1.5cqh,15px) clamp(9px,1.5cqh,15px);
  background:linear-gradient(168deg,#fcfbf8,#e9e7e1 62%,#dedbd3);
  border:1px solid rgba(255,255,255,.34);
  /* nothing to see through: this face is paper, not glass */
  -webkit-backdrop-filter:none;backdrop-filter:none;
  color:#15161a;
}
/* the print on this side sits on paper, so it casts nothing */
.pass-back>*{text-shadow:none}
/* the punched slot goes through the card, so it is on both sides of it */
.pass-back .pass-hole{background:rgba(0,0,0,.55);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.6),0 1px 0 rgba(255,255,255,.5)}

/* The code, and the reason there is a back at all. A vCard rather than a
   link: a scan offers to save a contact instead of opening another page. */
.pass-qr{
  /* Big, because a code that has to be aimed at from across a desk is not a
     decoration on the card — it is the card. At this width a module lands on
     roughly two and a half screen pixels, which is what a phone camera needs. */
  flex:none;width:74%;aspect-ratio:1;
  padding:3.5%;border-radius:3px;background:#fff;
  box-shadow:0 1px 3px rgba(0,0,0,.14),0 0 0 1px rgba(0,0,0,.06);
}
/* Left to antialias on purpose. Snapped to whole device pixels the modules
   come out unevenly wide at any fractional scale — some two pixels, some
   three — and a decoder reads that as a damaged code. A soft edge at an exact
   fractional position is the one a camera resolves. */
.pass-qr img{display:block;width:100%;height:100%}

.pass-scan{
  flex:none;margin-top:clamp(4px,.85cqh,9px);
  font-size:clamp(.4rem,.86cqh,.52rem);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:#606670;line-height:1.2;
}
html[lang="ar"] .pass-scan{font-family:"SF Pro AR",sans-serif;letter-spacing:0;text-transform:none}

.pass-lines{
  flex:none;width:100%;margin-top:clamp(5px,.9cqh,10px);
  padding-top:clamp(4px,.8cqh,8px);border-top:1px solid rgba(0,0,0,.13);
  display:flex;flex-direction:column;gap:2px;
  font-family:var(--font-num);
  font-size:clamp(.42rem,.9cqh,.55rem);font-weight:600;
  letter-spacing:.05em;color:#3c4048;line-height:1.35;
}

/* The line that makes it an issued object rather than a printed one. */
.pass-sign{flex:none;width:78%;margin-top:auto;padding-top:clamp(5px,1cqh,10px)}
.pass-sign b{
  display:block;font-family:var(--font-display);
  font-size:clamp(.6rem,1.45cqh,.84rem);font-weight:600;font-style:italic;
  letter-spacing:-.01em;color:#1a1c21;line-height:1.15;
}
html[lang="ar"] .pass-sign b{font-family:"SF Pro AR",sans-serif;font-style:normal;letter-spacing:0}
.pass-sign span{
  display:block;margin-top:3px;padding-top:3px;
  border-top:1px solid rgba(0,0,0,.24);
  font-size:clamp(.36rem,.78cqh,.46rem);font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;color:#71767f;line-height:1;
}
html[lang="ar"] .pass-sign span{font-family:"SF Pro AR",sans-serif;letter-spacing:0;text-transform:none}

/* the same number as the front, so the two faces are plainly one card */
.pass-backmeta{
  flex:none;width:100%;margin-top:clamp(5px,.9cqh,10px);
  display:flex;align-items:center;gap:clamp(5px,1cqh,9px);
}
.pass-backmeta .pass-code{
  flex:1;margin-top:0;opacity:.85;
  background-image:
    repeating-linear-gradient(90deg, rgba(0,0,0,.74) 0 2px, transparent 2px 5px),
    repeating-linear-gradient(90deg, rgba(0,0,0,.64) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(0,0,0,.56) 0 3px, transparent 3px 11px);
}
.pass-backmeta span{
  flex:none;font-family:var(--font-num);
  font-size:clamp(.38rem,.82cqh,.5rem);font-weight:600;
  letter-spacing:.12em;color:#71767f;line-height:1;
}

/* ---- the turn ----
   In the corner of the SCENE, not on the card. A control fixed to a swinging
   object moves while you are reaching for it, and the one thing a button has
   to do is still be where it was when you decided to press it. Off the card it
   also stops being print on a pass — which is what it looked like sitting a
   few pixels above the year — and becomes what it is: a control for the panel
   the pass hangs in. */
.pass-turn{
  position:absolute;z-index:4;
  top:clamp(10px,2.2cqh,18px);right:clamp(10px,2.2cqh,18px);
  width:clamp(28px,4.4cqh,34px);aspect-ratio:1;padding:0;
  display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.16);border-radius:50%;
  background:rgba(255,255,255,.05);color:rgba(255,255,255,.55);
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
  opacity:.55;cursor:pointer;
  transition:opacity .25s var(--ease),color .25s var(--ease),
             background .25s var(--ease),border-color .25s var(--ease);
}
/* dim until the pointer is on the button itself — lighting up whenever the
   panel was entered made it flicker at every pass over the card */
.pass-turn:hover,.pass-turn:focus-visible{
  opacity:1;background:rgba(255,255,255,.12);
  border-color:rgba(255,255,255,.3);color:#fff;
}
/* turned over it stays lit, because it is now the way back */
.pass-turn[aria-pressed="true"]{
  opacity:1;background:rgba(255,255,255,.14);
  border-color:rgba(255,255,255,.34);color:rgba(255,255,255,.92);
}
/* ---- on a touch screen it simply is ----
   Every rule above it is a hover rule, and a finger has no hover: a tap leaves
   the scene "hovered" until you touch somewhere else, so the mark lit up, went
   back to being lit, and read as flickering at the thing you were trying to
   look at. On a coarse pointer it holds one appearance and never reacts —
   still the same control, still in the same corner, just no longer a light
   that comes on. */
@media (pointer:coarse){
  .pass-turn,
  .pass-turn:hover,
  .pass-turn:focus-visible,
  .pass-turn[aria-pressed="true"]{
    opacity:1;transition:none;
    background:rgba(255,255,255,.05);
    border-color:rgba(255,255,255,.16);
    color:rgba(255,255,255,.55);
  }
}
.pass-turn svg{
  width:clamp(13px,2.1cqh,16px);height:auto;
  fill:none;stroke:currentColor;
  stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;
}

/* A pass that does not swing should not spin either.
   The whole loop returns early under this setting — see Pass.tsx — so nothing
   writes --flip and the card would never turn at all. This is the turn without
   the physics: the far side, arrived at instantly. */
@media (prefers-reduced-motion:reduce){
  .pass-scene.is-back .pass-card{--flip:180deg}
  .pass-turn{transition:none}
}

/* the highlight running across the face as it turns — the one thing that makes
   a flat rectangle read as a surface catching a light */
.pass-sheen{
  position:absolute;inset:0;border-radius:clamp(12px,1.9cqh,20px);pointer-events:none;
  transform:translateZ(1px);
  /* front only. It is a highlight on plastic, and the back is paper — left
     visible it showed through the back mirrored, which is a shine on a sheet
     that has no shine to give. */
  -webkit-backface-visibility:hidden;backface-visibility:hidden;
  /* Two bands, not one. A single narrow stripe at .4 white was a hard white
     bar travelling over the card — the shape of a gradient rather than the
     shape of a light. Real glass under a ceiling light carries a wide, dim
     wash with a soft core somewhere inside it, and the core is never as
     bright as the edge of the card it lies on.

     So: a broad half that reaches most of the way across the face and only
     ever gets to .1 white, and a softer core inside it at .16 that has no
     hard stop of its own. Both are drawn from the same --sheen, so they
     travel together as one light rather than as two passes. */
  background:
    linear-gradient(104deg,
      transparent calc(var(--sheen,50%) - 30%),
      rgba(255,255,255,.09) calc(var(--sheen,50%) - 15%),
      rgba(255,255,255,.16) var(--sheen,50%),
      rgba(255,255,255,.08) calc(var(--sheen,50%) + 16%),
      transparent calc(var(--sheen,50%) + 32%)),
    linear-gradient(104deg,
      transparent calc(var(--sheen,50%) - 72%),
      rgba(255,255,255,.045) calc(var(--sheen,50%) - 36%),
      rgba(255,255,255,.10) var(--sheen,50%),
      rgba(255,255,255,.045) calc(var(--sheen,50%) + 36%),
      transparent calc(var(--sheen,50%) + 72%));
  mix-blend-mode:screen;opacity:.62;
}

/* ---- one column, and a page that scrolls ----
   Below this the three columns are narrower than their own contents, so they
   stack — and a stacked page is a page you scroll, which is fine: the single
   screen was only ever a claim about a screen big enough to make it. */
@media (max-width:1079px), (max-height:619px){
  .pass-stage{
    grid-template-columns:minmax(0,1fr);
    padding-top:74px;padding-bottom:clamp(40px,7vh,80px);
    gap:14px;
  }
  /* Stacked, the column and the pair under it are not layout any more — they
     are wrappers around cards that now sit one under the other, and a wrapper
     is exactly what stops those cards being ordered against each other. So
     they stop existing as boxes: display:contents hands their children up to
     the stage, and the whole page becomes one ordered list of four cards.

     The order is the introduction the page makes in person: who this is, the
     thing itself, what he works in, and how much of it there has been.

     Tools comes straight after the pass because it is the fastest card on the
     page: a row of marks a reader takes in at a glance and, scrolling a phone,
     the one that earns the next scroll. The count follows it, where it reads
     as the size of what was just shown rather than as a claim made before
     anything backs it. */
  .pass-stage>.pass-col,
  .pass-stage .pass-under{display:contents}
  .pass-id  {order:1}
  .pass-hang{order:2}
  .pass-apps{order:3}
  .pass-now {order:4}
  .pass-scene{height:clamp(380px,64svh,560px);flex:none}
  /* Bigger, and sitting on the line it explains. The room the greeting gains
     is the room the gap under it gives up, so the card is the height it was —
     the words simply take the space the air was holding. */
  .pass-hello{font-size:clamp(3.2rem,14vw,4.6rem)}
  .pass-stage .pass-id{gap:8px}

  /* The deep foot under the marks is a rule about a card that has to fill a
     column of the window. Stacked, the card is only as tall as what is on it,
     so that padding is just a hole between the marks and the pass under them —
     the signature brings its own room with it. */
  .pass-stage .pass-id{padding-bottom:clamp(15px,1.4vw,23px)}

}
@media (max-width:520px){
  /* One row here too. Contact takes the width and the CV sits beside it at the
     width of the word on it — the same pair as on a desktop, which is what the
     page promises above it. Stacked they read as two calls to action, and the
     CV is a footnote to one. */
  .pass-acts{flex-direction:row;align-items:stretch}
  .pass-acts>.about-btn:first-child{flex:1 1 0;width:auto;min-width:0}
  .pass-acts>.about-btn:last-child{flex:0 0 auto;width:auto;padding:0 18px}
}

/* tall variant — 2 columns of stacked shots filling the card top to bottom */
.works-grid{display:grid;grid-template-columns:repeat(var(--work-cols,2),1fr);gap:14px;flex:1;align-content:stretch}
@media (max-width:760px){.works-grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}}
@media (max-width:560px){
  /* A phone gets two folders, not four. One tile across is the only honest
     layout at this width, and four of them turn a taste of the work into a
     scroll — the card says "see all work" in the corner and that is what the
     rest of them are for. Hidden rather than cut from the markup because the
     count is a shape decision, not a content one: /about and a wider window
     still show four. */
  .home-long .works-grid .shot:nth-child(n+3){display:none}
  /* And narrower. Stretched the full width of the phone the tile ran to 285px
     against 135px of paper, which left a hand's width of empty pocket down
     either side — the folder was wide, not the paper small. */
  .home-long .works-grid .shot{max-width:250px;margin-inline:auto;width:100%}
}
.works-grid .shot{aspect-ratio:auto;min-height:clamp(140px,15vw,230px)}
.shot{
  flex:1;aspect-ratio:4/3;border-radius:14px;border:1px solid var(--line);
  background:linear-gradient(150deg, var(--card-2), var(--card));
  position:relative;overflow:hidden;transition:box-shadow .45s var(--ease);
  box-shadow:0 6px 18px -10px rgba(0,0,0,.5);
}
.card:hover .shot{box-shadow:0 14px 30px -12px rgba(0,0,0,.6)}
/* the three accent tints that used to wash across these are gone with the rest
   of the colour — a shot is plain glass now */
/* The tile itself no longer moves. It used to lift, grow and tilt under the
   cursor, which turned a quiet grid into four cards flapping; the movement has
   gone inside instead — the tile holds its square and the paper in it shifts.
   See .sheets below. */
.shot:hover{box-shadow:0 16px 34px -14px rgba(0,0,0,.7)}
/* …except in the work grid, where the wallet itself never reacts. The paper
   inside it fans; the wallet holding the paper is the same object before and
   after, so it keeps one shadow throughout — deepening it made the tile lift
   off the card, which is the folder changing rather than the paper moving.
   Pinned past .card:hover as well, for the same reason. */
.works-grid .shot,
.card:hover .works-grid .shot,
.works-grid .shot:hover,
.works-grid .shot:has(.paper-hit:hover){box-shadow:0 6px 18px -10px rgba(0,0,0,.5)}

/* ---- the wallet ----
   A work tile is a pocket with paper in it: three sheets standing in the back
   and a panel across the front holding them down. Only the work grid gets it —
   the certificate plates on /about are the same .shot and are meant to stay
   plain, so everything here hangs off .works-grid.

   Nothing is a folder shape. The tile keeps its corners; the fold is only the
   line where the pocket meets the sheets. */
/* The tile keeps its edges, and the paper stays inside them.

   It ran unclipped for a while, on the reasoning that a leaning sheet sliced
   flat against the edge is the one thing paper never does. True of the sides,
   where nothing reaches the edge anyway — and wrong at the bottom, which is
   where the fan actually goes. The two back sheets pivot about their own
   bottom edge, so opening them swings their lower corners down and out, and
   they came through the floor of the tile as a wide V under the pocket: the
   wallet grew a pair of legs the moment the cursor arrived, and the card was
   a different shape hovered than at rest.

   Clipping costs nothing to look at, because everything near that edge is
   behind the pocket already — the band is 34% of the tile and nearly solid,
   so a corner that dips under it was never visible to begin with. What the
   clip removes is only the part that had left the tile altogether. */
.works-grid .shot{overflow:hidden;container-type:inline-size}
.works-grid .sheets{position:absolute;inset:0;z-index:1;pointer-events:none}
/* The paper is the only thing the cursor answers to. The wrapper stays
   transparent to the mouse so the tile's own empty corners do nothing, and
   each sheet takes the pointer back — so the fan opens when you are actually
   over a sheet, and not when you cross the pocket or the caption written on
   it. */
/* Nothing inside the tile is a hover target any more — not the sheets, not
   the pocket, not the caption written on it. A target that moves when it is
   hovered fights itself: the sheet swings out from under the cursor, the fan
   shuts, the sheet comes back, and the tile shakes for as long as the hand
   holds still. The trigger is .paper-hit below, which stands still. */
.works-grid .sheet,.works-grid .pocket{pointer-events:none}
/* The whole hover surface, and the only one — and it is the paper's own
   footprint, nothing else.

   It used to be the tile: full width, full height, which meant the fan opened
   when the cursor crossed the glass in the corner, or the pocket, or the
   caption written on it. The paper is the thing that moves, so the paper is
   the thing you have to be on. Same box as .sheet.s1 below, to the pixel — it
   is that sheet's shadow as far as the mouse is concerned, and it stands
   still while the sheet swings, which is what stops the tile shaking.

   It is also why "Coming soon" can be read with a mouse again: an invisible
   pane over the caption is a pane over the caption, and text under one cannot
   be selected. */
.works-grid .paper-hit{
  position:absolute;left:50%;bottom:34%;translate:-50% 0;
  /* the paper's own width, and the part of its height that is not behind the
     pocket: from the pocket's lip up to the top edge of the sheet */
  width:58cqw;
  height:calc(min(84%,78cqw) - 29%);
  z-index:4;
}
a.paper-hit{cursor:pointer}
.works-grid .sheet{
  /* Paper, not a plate: portrait, and sized off its own height so it keeps the
     proportions of a sheet whatever shape the tile around it is. Centred with
     the `translate` property rather than inside `transform`, so the fan below
     owns transform outright and does not have to carry the -50% with it. */
  /* Sized off its WIDTH, not the tile's height. Driven from the top and bottom
     edges it grew with the tile, and on the tall tiles a sheet that tall swung
     its corners clean past the sides when it leaned — which is what was being
     cut off. A sheet is now a fixed share of the tile's width, its height
     follows from the paper ratio, and it stands a little clear of the floor so
     no corner dips through it. The clearance is at the top, over the paper. */
  /* Whichever of the two fits: 78% of the tile's width laid out as a sheet, or
     84% of its height. A share of the width alone stood 220px of paper in the
     170px-tall tile the phone lays out, and with the tile no longer clipping,
     that spilled up over the card. Height is the definite side here, so the
     paper ratio settles the width from it. */
  position:absolute;top:auto;bottom:5%;left:50%;
  height:min(84%,78cqw);width:auto;aspect-ratio:1/1.34;translate:-50% 0;
  border-radius:5px;
  border:1px solid rgba(255,255,255,.30);
  /* Solid, not glass. A translucent sheet sitting on a translucent sheet on a
     dark tile came out as three shades of the same near-black, and paper you
     have to look for is not paper. These are flat greys, each one a step
     darker going back, so the stack reads at a glance. Neutral greys, with no
     blue in them: a grey mixed cool goes distinctly blue against a black tile
     under the card's own light, which paper never does. */
  background:linear-gradient(180deg,#7a7a7a,#585858);
  overflow:hidden;transform-origin:50% 100%;
  box-shadow:0 4px 14px -6px rgba(0,0,0,.85);
  transition:transform .5s var(--ease);
  /* Its own layer, held from the start rather than taken at the moment the
     transition begins. A layer promoted mid-move snaps to the pixel grid on
     the way in and back off it at the end, which is the flicker the paper had
     as it left and rejoined the stack. */
  will-change:transform;backface-visibility:hidden;
}
/* The depth is in the grey, not in opacity: the sheets behind are darker and
   already leaning, and the delay staggers the three so they open as a stack
   rather than as a block. */
.works-grid .sheet.s3{
  transition-delay:80ms;
  border-color:rgba(255,255,255,.20);
  background:linear-gradient(180deg,#4b4b4b,#373737);
  transform:rotate(5deg) translateY(-3px);
}
.works-grid .sheet.s2{
  transition-delay:40ms;
  border-color:rgba(255,255,255,.25);
  background:linear-gradient(180deg,#606060,#464646);
  transform:rotate(-5deg) translateY(-2px);
}
/* The front of the wallet: a straight lip across the bottom third, and glass
   rather than a wall. The paper carries on behind it, blurred and dimmed by
   the panel instead of cut off by it — which is the whole difference between
   a pocket and a band painted across the tile. Only this is see-through; the
   paper behind it stays solid. */
.works-grid .pocket{
  position:absolute;inset:auto 0 0 0;height:34%;z-index:2;
  border-radius:0 0 13px 13px;
  border-top:1px solid rgba(255,255,255,.28);
  background:linear-gradient(180deg,rgba(255,255,255,.10),rgba(255,255,255,.05));
  -webkit-backdrop-filter:blur(7px) brightness(.62);
  backdrop-filter:blur(7px) brightness(.62);
  box-shadow:0 -12px 22px -12px rgba(0,0,0,.9);
  /* The glass is as it was — a clear film over a brightness cut — and it is
     meant to be: darkening it made the front of the tile a black band, which
     is a lid rather than a pocket. What made it flicker was never its colour;
     it was the paper behind it being re-promoted to its own layer mid-move,
     which had the backdrop re-sampled against a surface that kept changing
     underneath it. The paper holds its layer from the start now (see .sheet
     above) and the panel is composited once here, which settles it without
     touching how it looks. */
  transform:translateZ(0);
}
/* Hover: the stack opens. The two behind swing out to either side and the top
   sheet rises between them — a hand fanning three papers, not three sheets
   nudging.

   13 degrees was too far, and the note above it that the widest sheet still
   did not reach the tile's edge was simply wrong: measured against the tile's
   own outline, a sheet at 13° crossed it on the side and again underneath,
   which is where the wallet appeared to sprout a wide V the moment the cursor
   arrived. 8° with half the sideways shove reads as the same gesture and
   stays inside the tile at every size it is drawn at, so the clip above never
   has anything to cut and the tile is the same shape hovered as at rest. */
.works-grid .shot:has(.paper-hit:hover) .sheet.s1{transform:translateY(-9px)}
.works-grid .shot:has(.paper-hit:hover) .sheet.s2{transform:rotate(-8deg) translate(-5%,-5px)}
.works-grid .shot:has(.paper-hit:hover) .sheet.s3{transform:rotate(8deg) translate(5%,-4px)}
/* Someone who asked for less motion still gets the wallet — it is a drawing,
   not an animation — but the paper stops sliding. */
@media (prefers-reduced-motion:reduce){
  .works-grid .sheet{transition:none}
  .works-grid .shot:has(.paper-hit:hover) .sheet.s1{transform:none}
  .works-grid .shot:has(.paper-hit:hover) .sheet.s2{transform:rotate(-5deg) translateY(-2px)}
  .works-grid .shot:has(.paper-hit:hover) .sheet.s3{transform:rotate(5deg) translateY(-3px)}
}

/* A shot with a real piece of work in it. Empty slots keep the plain-glass
   look above; these get the image on the top sheet and the caption over the
   pocket. */
.work{display:block;text-decoration:none;color:inherit}
.work-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* The caption is written on the front of the pocket, not over the paper: a
   name and one short line under it, sitting in the band where the glass has
   already dimmed whatever is behind it. It needs no scrim of its own — the
   gradient it used to carry was there to hold it off a full-bleed image, and
   the image is on the paper now. */
.work-meta{
  /* The band IS the pocket: same height, so the two lines centre themselves in
     it rather than being pushed down against the tile's floor. */
  position:absolute;inset:auto 0 0 0;height:34%;z-index:3;
  display:flex;flex-direction:column;justify-content:center;align-items:flex-start;
  /* Measured against the TILE, not the window: the same caption sits in a
     220px tile on the home page and a 124px one in the /about column, and a
     window-sized clamp set both to the same 17px — which the small tile wore
     like a shout. cqw is a percent of the tile's own width, so the words
     shrink with the pocket they are written on. */
  gap:clamp(3px,2.6cqw,6px);padding-inline:clamp(8px,5.6cqw,18px);
}
.work-meta b{
  font-family:var(--font-display);font-size:clamp(.8rem,8.2cqw,1.22rem);
  font-weight:600;color:#fff;line-height:1.2;
  /* one line, whatever the title's length — a name that wraps pushes the line
     under it out of the pocket and onto the paper */
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.work-meta em{
  font-style:normal;font-size:clamp(.52rem,5.2cqw,.8rem);color:rgba(255,255,255,.6);
  line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.work-meta i{font-style:normal;font-size:clamp(.5rem,4.6cqw,.7rem);letter-spacing:.08em;color:rgba(255,255,255,.4)}
/* On a small tile the name is all there is room for: a second line in a
   pocket that narrow is two crushed lines rather than a caption, so below this
   width the tile keeps the title and drops the rest. Asked of the TILE, not
   the window — it is the pocket that has no room, wherever it is.

   The mark was 190px while the /about plates were squares held to the height
   of a column; landscape, they carry the line comfortably a good deal
   narrower, and 190 was cutting the caption off a pocket with room for it on
   an ordinary laptop. */
@container (max-width:160px){
  .works-grid .work-meta em,.works-grid .work-meta i{display:none}
}

/* A quiet way out of a card — the text equivalent of .cta-btn, for the places
   where a filled button would shout over the card it sits in. */
.more-link{
  margin-top:auto;align-self:flex-start;
  display:inline-flex;align-items:center;gap:8px;
  font-size:clamp(.92rem,1.05vw,1rem);font-weight:600;
  color:var(--ink-dim);text-decoration:none;
  transition:color .3s var(--ease);
}
.more-link:hover{color:var(--ink)}
.more-link svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;transition:transform .3s var(--ease)}
.more-link:hover svg{transform:translateX(3px)}
html[lang="ar"] .more-link svg{transform:scaleX(-1)}
html[lang="ar"] .more-link:hover svg{transform:scaleX(-1) translateX(3px)}

.pills{display:flex;flex-wrap:wrap;gap:7px;align-content:flex-start}
.pillt{
  padding:11px 20px;border-radius:var(--r-pill);border:1px solid var(--line);
  background:var(--card-2);font-size:clamp(.92rem,1.05vw,1.05rem);font-weight:600;color:var(--ink-dim);
  transition:color .3s var(--ease);
}
/* pills hold their size and border — hover only brightens the label to white */
.pillt:hover{color:var(--ink)}

/* ================= THE RECORD (/resume) =================
   The panels that used to sit under /about's fold, back on the page they were
   always meant for. They came off /about when the cover became one screen;
   they did not stop being true, and a résumé page that lists nothing is not a
   résumé page.

   The cards are set tighter than the site's default card: the same hairline
   and radius, less padding, and a label at caption size rather than at title
   size — nine panels read as a record, and nine panels each shouting its own
   name reads as nine pages. */
.resume-page .grid{row-gap:clamp(16px,1.6vw,26px);column-gap:clamp(16px,1.6vw,26px)}
.resume-page .card{
  padding:clamp(17px,1.6vw,26px);
  gap:clamp(8px,.8vw,14px);
  border-radius:clamp(18px,1.6vw,26px);
  border-color:var(--line);
  min-width:0;
}
.resume-page .card .label{font-size:clamp(.88rem,1.02vw,1.08rem);letter-spacing:.16em}
html[lang="ar"] .resume-page .card .label{letter-spacing:.06em}
.resume-page .card p{font-size:clamp(.86rem,.95vw,.98rem);line-height:1.6}

/* ---- the three timelines ----
   A rule down the side with a mark on each entry, which is what makes four
   dated lines read as a run of time rather than as a list that happens to
   carry dates. */
.timeline{display:flex;flex-direction:column;gap:clamp(13px,1.4vw,20px)}
.tl-item{
  position:relative;padding-inline-start:19px;min-width:0;
  display:flex;flex-direction:column;gap:1px;
}
/* the rule: from this entry's own mark down to the next one's */
.tl-item::before{
  content:"";position:absolute;inset-inline-start:3px;top:9px;bottom:calc(-1 * clamp(13px,1.4vw,20px));
  width:1px;background:var(--line);
}
.tl-item:last-child::before{display:none}
.tl-item::after{
  content:"";position:absolute;inset-inline-start:0;top:5px;
  width:7px;height:7px;border-radius:50%;
  border:1px solid var(--line-hi,var(--line));background:var(--card-2,rgba(255,255,255,.06));
}
.tl-item b{font-size:clamp(.92rem,1.02vw,1.04rem);font-weight:700;color:var(--ink);line-height:1.35}
.tl-item span{font-size:clamp(.8rem,.88vw,.9rem);color:var(--ink-dim);line-height:1.5}
.tl-item time{
  font-size:.72rem;font-weight:600;letter-spacing:.07em;
  color:var(--ink-dim);opacity:.65;margin-top:3px;
}

/* ---- certificates: plates, at the size a paper reads at ---- */
.certs{display:grid;grid-template-columns:repeat(auto-fit,minmax(88px,1fr));gap:11px;margin-top:auto}
.certs .shot{aspect-ratio:4/3;flex:none}

/* ---- languages ---- */
.langs{display:flex;flex-direction:column;gap:clamp(11px,1.2vw,16px);margin-top:auto}
.lang-row{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
.lang-row > b{font-size:clamp(.92rem,1.02vw,1.04rem);font-weight:700;color:var(--ink)}
.lr-level{display:flex;align-items:center;gap:11px}
.lr-level span{font-size:.78rem;color:var(--ink-dim)}
.dots{display:flex;gap:5px}
.dots i{
  width:7px;height:7px;border-radius:50%;
  border:1px solid var(--line-hi,var(--line));background:transparent;
}
.dots i.on{background:var(--ink);border-color:transparent;opacity:.85}

/* The ask sits last and reads as an aside, not as a tenth section of the
   record — so its buttons sit on a row of their own with air above them. */
.resume-page .resume-card .about-actions{margin-top:clamp(4px,.6vw,10px)}

/* — shadcn-style pieces (badge / separator) —
   The timeline that used to sit here went with the old /about; these two are
   still in use on the checkout and in the country menu. */
.badge{
  display:inline-flex;align-items:center;gap:5px;
  padding:3px 11px;border-radius:var(--r-pill);
  border:1px solid var(--line-hi);background:rgba(255,255,255,.04);
  font-size:.76rem;font-weight:600;color:var(--ink);line-height:1.6;
  transition:background .2s var(--ease);
}
.badge:hover{background:rgba(255,255,255,.09)}
.badges{display:flex;flex-wrap:wrap;gap:8px}
.separator{height:1px;background:var(--line);border:0;margin:4px 0}

/* app icons — App Store squircles (Apple-hosted + local); rendered as a slow marquee */
.app-strip{display:flex;flex-wrap:wrap;gap:10px;margin-top:auto}
.app-strip.marquee{flex-wrap:nowrap;padding-block:6px}
.app-strip .app-icon{flex:none}
.app-icon{
  --app-icon-mask:url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20230.5%20230.5%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M158.2%20230H64.1a320%20320%200%200%201-7-.1c-5%200-10-.5-15-1.3a50.8%2050.8%200%200%201-14.4-4.8%2048.2%2048.2%200%200%201-21-21%2050.9%2050.9%200%200%201-4.8-14.4%20100.7%20100.7%200%200%201-1.3-15v-7l-.1-8.2V64.1a320%20320%200%200%201%20.1-7c0-5%20.5-10%201.3-15a50.7%2050.7%200%200%201%204.8-14.4%2048.2%2048.2%200%200%201%2021-21%2051%2051%200%200%201%2014.4-4.8c5-.8%2010-1.2%2015-1.3a320%20320%200%200%201%207%200l8.2-.1h94.1a320%20320%200%200%201%207%20.1c5%200%2010%20.5%2015%201.3a52%2052%200%200%201%2014.4%204.8%2048.2%2048.2%200%200%201%2021%2021%2050.9%2050.9%200%200%201%204.8%2014.4c.8%205%201.2%2010%201.3%2015a320%20320%200%200%201%20.1%207v102.3l-.1%207c0%205-.5%2010-1.3%2015a50.7%2050.7%200%200%201-4.8%2014.4%2048.2%2048.2%200%200%201-21%2021%2050.8%2050.8%200%200%201-14.4%204.8c-5%20.8-10%201.2-15%201.3a320%20320%200%200%201-7%200l-8.2.1z%22%2F%3E%3C%2Fsvg%3E');
  width:56px;height:56px;display:inline-grid;place-items:center;
  -webkit-touch-callout:none;user-select:none;-webkit-user-select:none}
.app-icon img{width:100%;height:100%;object-fit:contain;
  user-select:none;-webkit-user-select:none;-webkit-user-drag:none;pointer-events:none;
  mask-image:var(--app-icon-mask);-webkit-mask-image:var(--app-icon-mask);
  mask-size:100% 100%;-webkit-mask-size:100% 100%}
/* local logos aren't App Store artwork — put them on a tile and mask the tile instead */
.app-icon.local{background:linear-gradient(145deg,#fff,#ececec);
  mask-image:var(--app-icon-mask);-webkit-mask-image:var(--app-icon-mask);
  mask-size:100% 100%;-webkit-mask-size:100% 100%}
.app-icon.local img{mask-image:none;-webkit-mask-image:none}
/* .pad — for transparent logos that shouldn't touch the tile edges */
.app-icon.local.pad img{width:72%;height:72%}
/* .plain — a logo, not an app icon.
   A language has no app icon: it has a mark, and the mark is the whole of it.
   Sitting one on the same white squircle the App Store artwork gets turns a
   row of logos into a row of stickers — so the tile, its gradient and the
   squircle mask all come off, and what is left is the mark on the card. It is
   drawn a little inside the slot so that a wide mark and a tall one read as
   the same size beside each other. */
.app-icon.local.plain{background:none;mask-image:none;-webkit-mask-image:none}
.app-icon.local.plain img{width:78%;height:78%;object-fit:contain;
  mask-image:none;-webkit-mask-image:none}

/* group logos — clickable tiles; auto-marquee when >4 (see main.js) */
.logos{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px;margin-top:auto}
.logo{aspect-ratio:16/6.6;border-radius:20px;border:1px solid var(--line);background:var(--card-2);
  display:grid;place-items:center;color:var(--ink-faint);font-size:.7rem;letter-spacing:.18em;
  text-decoration:none;overflow:hidden;transition:color .3s var(--ease),border-color .3s var(--ease)}
.logo:hover{color:var(--ink)}
.logo img{max-width:64%;max-height:56%;object-fit:contain}
/* marquee mode (shared): GPU-smooth transform track — auto-drifts,
   wheel / drag / swipe adjust the same offset */
.marquee{display:flex;overflow:hidden;direction:ltr;cursor:grab;touch-action:pan-y;
  min-width:0;max-width:100%;user-select:none;-webkit-user-select:none;
  mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent)}
.marquee.dragging{cursor:grabbing}
/* the strip is the interaction surface — individual logos / app icons are not
   click targets (avoids accidental navigation & selection while scrolling) */
.marquee .logo{pointer-events:none}
/* app icons stay hoverable (for the name tip) but never navigate — they're
   rendered as plain <span>, and drag is handled on the strip container */

/* themed app-name tooltip — a body-level element (JS-positioned) so it isn't
   clipped by the strip's overflow:hidden / mask */
.app-tip{position:fixed;z-index:200;pointer-events:none;
  padding:5px 10px;border-radius:9px;white-space:nowrap;
  font-size:.72rem;letter-spacing:.02em;color:var(--ink);
  background:color-mix(in srgb,var(--card-2) 92%,transparent);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  border:1px solid var(--line-hi);
  box-shadow:0 10px 30px -12px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.06);
  opacity:0;transform:translate(-50%,calc(-100% + 4px));
  transition:opacity .18s var(--ease),transform .18s var(--ease)}
.app-tip.show{opacity:1;transform:translate(-50%,-100%)}
.app-tip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:var(--line-hi)}
.marquee .mq-track{display:flex;gap:10px;width:max-content;will-change:transform}
.marquee .mq-set{display:flex;gap:10px;align-items:center}
.logos.marquee{padding-block:8px}
.logos.marquee .logo{width:360px;flex:none}

.stats{display:flex;gap:12px;margin-top:auto}
.stat{flex:1;padding:18px 14px;border-radius:12px;border:1px solid var(--line);background:var(--card-2);text-align:center}
.stat b{display:block;font-family:var(--font-num);font-size:clamp(2rem,2.8vw,2.6rem);font-weight:700;letter-spacing:-.02em}
/* --ink-dim, not --ink-faint: the tile is glass over the card's light now, and
   the faint grey stopped reading against the brighter hues */
.stat span{font-size:clamp(.7rem,.85vw,.8rem);letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim)}
/* placeholder figure while the counts stream in — same box, no number yet */
.stat b.stat-wait{border-radius:6px;background:var(--line);opacity:.5;
  width:2.4ch;margin-inline:auto;animation:stat-wait 1.1s ease-in-out infinite}
@keyframes stat-wait{50%{opacity:.22}}
@media (prefers-reduced-motion:reduce){.stat b.stat-wait{animation:none}}

/* — INNER GLASS —
   Everything that sits inside a card: the stat tiles, the logo plates, the work
   shots and the pills. They were solid slabs of --card-2, which on a lit card
   read as black holes punched through the light. Same clear-glass recipe as the
   card itself, only lighter — real backdrop blur here, because these are small
   boxes well inside the card and nothing of theirs hangs over an edge. The
   card's colour comes through them frosted instead of being blocked. */
.stat,.logo,.shot,.pillt{
  background:
    /* the sheen across the top-left, same angle as the card's */
    linear-gradient(150deg, rgba(255,255,255,.10), transparent 46%, rgba(255,255,255,.03) 78%),
    /* and a dark scrim under it. This is the part that matters: these tiles sit
       right in the brightest part of the card's light, and glass that only adds
       white would blow out to a flat wash there. Darkening what shows through
       is what keeps the numbers and the labels readable on every hue. */
    linear-gradient(to top, rgba(0,0,0,.38), rgba(0,0,0,.2));
  backdrop-filter:blur(10px) saturate(140%);
  -webkit-backdrop-filter:blur(10px) saturate(140%);
  border:1px solid rgba(255,255,255,.12);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.10),
    0 6px 18px -14px rgba(0,0,0,.6);
}

.cta-btn{
  position:relative;align-self:flex-start;margin-top:auto;display:inline-flex;align-items:center;gap:9px;
  padding:16px 28px;border-radius:var(--r-pill);text-decoration:none;overflow:hidden;
  background:rgba(255,255,255,.1);
  backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);
  border:1px solid rgba(255,255,255,.3);
  box-shadow:
    0 8px 24px rgba(0,0,0,.25),
    inset 0 1px 0 rgba(255,255,255,.5),
    inset 0 -1px 0 rgba(255,255,255,.08);
  color:var(--ink);font-size:clamp(.98rem,1.15vw,1.1rem);font-weight:700;
  transition:all .3s var(--ease);
}
/* top edge light, matching the hero glass */
.cta-btn::before{
  content:"";position:absolute;top:0;left:0;right:0;height:1px;
  background:linear-gradient(90deg, transparent, var(--edge), transparent);
}
.cta-btn:hover{transform:translateY(-2px);background:rgba(255,255,255,.16);box-shadow:0 14px 34px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.6)}
.cta-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;transition:transform .3s var(--ease)}
.cta-btn:hover svg{transform:translateX(3px)}

/* Put something new on the wall — the send button on the contact page, cut
   down to the height of the bar. Not its own colour: it was green, from a
   token of its own, and on a wall that is nothing but grey a green pill is the
   loudest thing for three pages in either direction. The same white glass Send is cut
   from is enough to find on an empty wall, and it means the one button here
   and the one button there are recognisably the same control rather than two
   inventions.

   So the fill, the edge and the mark all come from .cta-btn unchanged; what is
   left below is only the two things a button in a 49px bar needs that a button
   at the foot of a card does not — the size, and not hopping when the pointer
   arrives. */
.create-btn{
  margin-top:0;align-self:center;
  /* Wider than the mark inside it needs. It is the one thing on the page that
     can be pressed, and on a wall with nothing on it the button's own width is
     most of what says so — a plus in a circle is an icon, a plus on a broad
     pill is a button. */
  height:35px;padding:0 26px;gap:0;
  font-size:.9rem;
}
/* The lift that .cta-btn does on hover is taken back off. That button stands
   at the bottom of a card with room over it; this one is fixed in a 49px bar,
   and a control that hops when the pointer reaches it is a control that moves
   out from under the pointer. So it stays exactly where it is and answers in
   light instead: the glass brightens where it would have risen, and a soft
   ring is thrown outward so the answer carries on a dark wall. */
.create-btn:hover{
  transform:none;
  background:rgba(255,255,255,.2);
  border-color:rgba(255,255,255,.45);
  box-shadow:
    0 8px 24px rgba(0,0,0,.28),
    0 0 0 4px rgba(255,255,255,.08),
    inset 0 1px 0 rgba(255,255,255,.5);
}
/* The plus is drawn heavier than the arrows elsewhere on the site. Those are
   marks inside sentences; this one is alone on an empty wall with no word
   beside it to carry it, and a hairline cross at that size reads as a hint
   rather than as the thing to press. Square caps rather than round: a plus
   with rounded ends is a soft suggestion, and this is a button. */
.create-btn svg{width:19px;height:19px;stroke-width:3.2;stroke-linecap:square}
/* the plus stays put: .cta-btn's mark travels sideways because it is an arrow
   leading somewhere, and this one is not going anywhere */
.create-btn:hover svg{transform:none}

/* ================= BIG GLASS FOOTER ================= */
/* same Clear-glass treatment as the hero card and the cards — dark, readable */
.site-footer{
  position:relative;z-index:1;
  --f-panel:rgba(255,255,255,.07); --f-ink:var(--ink);
  --f-dim:#b6b7bf; --f-faint:#94959e;
  background:
    linear-gradient(115deg, rgba(255,255,255,.07), transparent 45%, rgba(255,255,255,.03) 75%),
    linear-gradient(to top, rgba(0,0,0,.2), transparent 55%),
    rgba(255,255,255,.045);
  backdrop-filter:blur(2px) saturate(180%);
  -webkit-backdrop-filter:blur(2px) saturate(180%);
  color:var(--f-ink);
  border-top:1px solid rgba(255,255,255,.3);
  border-top-left-radius:var(--r-xl);border-top-right-radius:var(--r-xl);
  box-shadow:inset 0 1.5px 0 rgba(255,255,255,.4), inset 0 40px 80px -60px rgba(255,255,255,.12);
  margin-top:60px;padding:clamp(48px,7vw,92px) clamp(20px,5vw,64px) 0;
}
html[lang="ar"] .site-footer{direction:rtl}
.foot-top{
  width:min(1340px,100%);margin-inline:auto;
  display:grid;grid-template-columns:1.5fr 1fr 1fr;gap:clamp(28px,5vw,64px);
  padding-bottom:62px;
}
/* logical properties → correct alignment in both EN and AR */
.foot-brand{text-align:start}
.foot-brand .flogo{height:62px;width:auto;display:block;margin-bottom:18px;margin-inline-start:0;filter:invert(1)} /* dark glass footer — invert the black logo */
.foot-brand p{color:var(--f-dim);font-size:1.02rem;line-height:1.65;max-width:36ch;text-align:start}
/* same size as the links below them */
.foot-col h4{font-family:var(--font-body);font-size:1.06rem;font-weight:600;color:var(--f-ink);margin-bottom:12px}
html[lang="ar"] .foot-col h4{letter-spacing:0}
.foot-col a{
  display:flex;align-items:center;gap:8px;width:fit-content;
  color:var(--f-faint);text-decoration:none;font-size:1rem;font-weight:500;
  padding:4px 0;transition:color .2s var(--ease);
}
.foot-col a:hover{color:var(--f-ink)}
/* The address itself stays left-to-right, so in Arabic the icon reads better
   at the far left of the line than pinned to the right of the text. */
html[lang="ar"] .foot-col a{flex-direction:row-reverse}
.foot-col a svg{width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}
/* Copy affordance on the address. The icon is always laid out, so the line
   never reflows on hover; it is shown by colour rather than opacity, for the
   same pixel-grid reason as the marks below. Once copied it turns into a
   green tick, whether or not the pointer is still there. */
.foot-email-mark{display:grid;width:15px;height:15px;flex-shrink:0}
.foot-email-mark svg{grid-area:1/1;width:15px;height:15px;transition:color .2s var(--ease)}
.foot-email .foot-email-copy,.foot-email .foot-email-tick{color:transparent}
.foot-email:hover .foot-email-copy,.foot-email:focus-visible .foot-email-copy{color:var(--f-faint)}
/* No drawing and no hand-over: the two marks trade colours on the same timing
   as the address itself does on hover. Each keeps its own colour, so the copy
   mark can never come back green. */
.foot-email.is-copied .foot-email-copy{color:transparent}
.foot-email .foot-email-tick{stroke-width:2.2}
.foot-email.is-copied .foot-email-tick{color:var(--good)}
/* Marks with nothing drawn around them, the same as the ones on /about. A box
   each was five more edges in a corner of the page that is already quiet, and
   the boxes were the loudest thing in it. Taking them off leaves the row a
   little tighter, so the gap carries the separation the borders used to. */
.foot-social{display:flex;gap:14px;margin-top:20px}
.foot-social a{
  width:26px;height:26px;display:grid;place-items:center;
  background:none;border:0;color:#86878d;
  transition:color .24s cubic-bezier(.4,0,.2,1);
}
.foot-social a:hover,.foot-social a:focus-visible{color:var(--f-ink)}
.foot-social a:focus-visible{outline:2px solid var(--a2);outline-offset:5px;border-radius:4px}
/* Hover is brightness and nothing else — the mark never resizes and never
   moves, which is how every other mark on the site behaves. The brightness is
   a colour fade rather than an opacity one, so the row cannot be promoted to
   a layer mid-transition and shifted onto the pixel grid under it; the tone at
   rest is what .72 opacity was already painting here. Same rule, same curve,
   on all three rows, so a mark behaves the same wherever it is met. */
.foot-social svg{width:100%;height:100%;fill:currentColor}
/* The line and the copyright under it are the quietest thing on the page —
   a rule and a credit, not content. Both are set below --f-faint rather than
   on it, because the footer's nav links borrow that same grey and should not
   go down with them. */
.foot-bottom{
  width:min(1340px,100%);margin-inline:auto;
  /* flex-start, not left: it lines up under the brand column above it, which
     is the edge the footer starts at — the right one on the Arabic page */
  display:flex;align-items:center;justify-content:flex-start;gap:16px;flex-wrap:wrap;
  padding:22px 0 30px;border-top:1px solid rgba(255,255,255,.055);
  color:rgba(255,255,255,.34);font-size:.76rem;
}
@media (max-width:720px){
  .foot-top{grid-template-columns:1fr 1fr}
  .foot-brand{grid-column:1 / -1}
}

/* ================= ROUTE PROGRESS =================
   Shown only while a client-side navigation is in flight. It rides above the
   nav (z-index 100) so it stays visible over the blurred header. */
.nav-progress{position:fixed;top:0;left:0;right:0;height:3px;z-index:110;pointer-events:none}
.nav-progress .bar{
  display:block;height:100%;width:100%;transform-origin:left center;
  /* Clear white glass, in the same key as .hero-card and .site-footer: a faint
     gradient over a barely-there fill, lit only by the blur behind it. No glow
     and no shadow — the bar should read as a pane of glass sliding across the
     top of the page, not as a lit strip. */
  background:
    linear-gradient(90deg, rgba(255,255,255,.05), rgba(255,255,255,.12) 60%, rgba(255,255,255,.16));
  backdrop-filter:blur(18px) saturate(180%);
  -webkit-backdrop-filter:blur(18px) saturate(180%);
  border-end-end-radius:2px;border-start-end-radius:2px;
  /* Creeps toward 90% on a deliberately decelerating curve: the wait has no
     known length, so the bar must never actually reach the end on its own. */
  animation:nav-progress-creep 8s cubic-bezier(.1,.8,.2,1) forwards;
}
/* The run-out target is set inline by NavProgress, which first pins the bar's
   live width so this transition has somewhere to travel from. */
.nav-progress .bar.done{transition:transform .24s ease-out,opacity .2s ease-in .24s;opacity:0}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  /* no blur to light it, so the fill carries the whole bar — still faint */
  .nav-progress .bar{background:rgba(255,255,255,.28)}
}
@keyframes nav-progress-creep{from{transform:scaleX(.02)}to{transform:scaleX(.9)}}
html[dir="rtl"] .nav-progress .bar{transform-origin:right center}
@media (prefers-reduced-motion:reduce){
  /* No creep, but the wait still needs a signal — hold a static bar instead. */
  .nav-progress .bar{animation:none;transform:scaleX(1);opacity:.5}
  .nav-progress .bar.done{opacity:0}
}

/* ================= LOAD GATE =================
   PageLoad holds the page back until its images and fonts have settled, so it
   arrives whole instead of assembling itself in front of the visitor. Nothing
   stands in for it meanwhile — no placeholder shapes, no pulse; the progress
   bar is the only signal. The content stays laid out underneath — hidden, not
   removed, because a display:none image is never fetched — which also means
   the scrollbar is already the finished page's. */
.page-gate{position:relative}
/* the nav is real from the first frame; only the page body waits */
.page-gate.is-loading > *:not(.nav){visibility:hidden}

/* ================= PAGE ENTER =================
   Plays the moment the gate lifts, so the finished page rises up as one piece
   rather than each part appearing as it loads. It comes from below and eases
   out over half a second — far enough to be felt as movement, slow enough to
   read as smooth rather than as a flick.

   It runs on the gate's children, never on the gate itself: a transform or
   opacity on an ancestor of the fixed nav would make that ancestor its
   containing block, and the header would detach for the length of the fade.
   The nav is a sibling here, so it is simply left out. */
.page-gate:not(.is-loading) > *:not(.nav){animation:page-enter .55s var(--ease) both}
@keyframes page-enter{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.page-gate > *{animation:none}}

/* ================= REVEAL ================= */
.reveal{opacity:0;transform:translateY(26px);transition:opacity .8s var(--ease), transform .8s var(--ease);transition-delay:var(--d,0ms)}
.reveal.in{opacity:1;transform:none}

/* ================= RESPONSIVE =================
   Phone-only corrections. The desktop design above is untouched: everything
   here exists because a tall, narrow viewport changes the proportions the
   desktop values were chosen for. */
@media (max-width:640px){
  .nav a{padding:8px 9px;font-size:.78rem}

  /* ---- the bar goes to marks ----
     Three words, a logo and the language button do not fit across a phone.
     They did go in, at .78rem and 9px of padding, but only by being shrunk
     to the size of small print — a header that has to whisper to fit is a
     header the page has outgrown.

     So on a phone the words come off and the marks take their place, and the
     row becomes four buttons of one size: home, about, contact, language.
     The words are not removed, only taken off the screen, so each link still
     announces itself by name to a screen reader. */
  .nav .nav-center{gap:6px}
  /* The marks keep their places in both languages. The words swap ends with
     the language, because a word is read in a direction — a house is not.
     Reversed, the three marks moved under the reader's thumb every time the
     language changed, and the mark for the page you are on was somewhere
     else than it was a moment ago. So the row is pinned LTR here: home,
     about, contact, in that order, on every page in either language. */
  html[lang="ar"] .nav .nav-center{direction:ltr}
  /* The desktop pill, with a mark in it instead of a word. A square on a pill
     radius is a circle, and four circles across the top of a phone are not
     the shape this header is anywhere else — the pill is, so the buttons keep
     it and only lose the width the word was taking. Height comes straight
     from the desktop rule; 52 wide holds the same proportion a short word
     gave it. */
  .nav .nav-center a{width:52px;padding:0;justify-content:center}
  .nav-word{
    position:absolute;width:1px;height:1px;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;
  }
  .nav-ico{display:block}
  /* A brighter word was how the current page said so, and there is no word
     here to brighten. It wears the glass plate the others only get on hover
     instead — the same object, held on rather than passing through. */
  .nav .nav-center a.active{color:var(--ink);border-color:rgba(255,255,255,.25)}
  .nav .nav-center a.active::before{opacity:1}
  /* The language button joins that row rather than sitting beside it as a
     wider pill: same 38px circle, and its letter alone. The globe comes off
     with the words — the letter already says which language, and a glyph that
     only says "language" next to it is the one mark in the row naming its
     category instead of its destination. */
  /* The language button joins that row rather than standing next to it as a
     different kind of control. It was the only one of the four wearing a
     border and the only one set in full white, so it read as a button beside
     three marks. Same box, same radius, same dim ink until it is reached
     for — the letter is the only thing that makes it different, which is the
     one difference it should have. */
  #lang{
    width:52px;min-width:0;padding:0;
    border-color:transparent;color:var(--ink-dim);font-size:.9rem;
  }
  #lang:hover{color:var(--ink)}
  #lang .lang-ico{display:none}
  :root[lang="en"] #lang-label{font-size:.88rem}
  /* Back where it was: 34 read as a mark that had been shrunk to fit rather
     than as the name at the head of the page. */
  .brand img{height:40px;margin-block:-3px}
  /* The card stacks, and fills the screen between the nav and the scroll cue
     instead of shrinking to its text — on a phone a short card leaves two
     large empty bands with nothing in them. The card is centred in the hero,
     so the 260px left over is split between the two ends: enough above for
     the nav, and enough below to leave the scroll cue clear air — pressed up
     against the glass it read as part of the card rather than as an
     invitation to move past it. */
  .hero-card,html[lang="ar"] .hero-card{
    flex-direction:column;justify-content:center;text-align:center;
    min-height:calc(100svh - 260px);padding:36px 24px;gap:20px;
  }
  /* it takes the leftover width in the desktop row; in this column that would
     make it take the leftover height too, pinning the text to the top */
  .hero-text{flex:0 0 auto}
  /* the name stays centred over the card; the line beneath it is prose, so it
     keeps a straight left edge (right, in Arabic) instead of ragging both ways */
  .hero-text .sub{margin-inline:0;text-align:start}
  .span-3{grid-column:span 6}
  /* The backdrop is a wide photograph; covering a phone screen crops it to a
     narrow column, which by default lands on the empty right half. Framed on
     the light instead, so the glow sits behind the card. */
  .hero::before{background-position:42% 42%}
  /* Five tiles across a phone leaves each one squat, where the desktop radius
     reads as a lozenge rather than a rounded square. Taller boxes, tighter
     corners, and an icon scaled to the smaller tile. */
  .social{gap:8px}
  .tile{aspect-ratio:1.15;border-radius:12px}
  .tile svg{width:clamp(24px,52%,34px)}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none !important;transition-duration:.001ms !important}
  html{scroll-behavior:auto}
  .reveal{opacity:1;transform:none}
  .hero-stage{transform:none !important}
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .hero-card{background:color-mix(in srgb, var(--card) 88%, transparent)}
  /* Without a blur there is nothing holding the type up, so the glass falls
     back to a solid — still the card's grey rather than the page's black. */
  .nav,.nav.scrolled{background:var(--card)}
}

/* ==========================================================================
   PAYMENTS — /pay
   ==========================================================================
   Built entirely from the primitives above: .nav, .page, .page-title, .grid,
   .card, .label, .field and .cta-btn are the site's own, so the checkout
   inherits the fonts, the glass, the radii and the Arabic behaviour for free.
   Everything below either arranges those pieces or adds the few things a
   payment page needs and no other page does — the total, the wallet buttons
   and the brand marks. */

.pay-page{padding-top:118px}

/* ------------------------- one screen, no scrolling -------------------------
   A payer who has to scroll to find the pay button is a payer deciding whether
   to bother. On any viewport with the room for it, the checkout is pinned to the
   viewport height and the two cards scroll INTERNALLY if their content outgrows
   them — the page itself never moves. Below that, it stacks and scrolls normally,
   because a fixed-height phone layout is how you end up with a pay button under
   the keyboard. */
.pay-checkout{width:min(1180px,100%);padding-bottom:26px}

@media (min-width:900px) and (min-height:620px){
  .pay-checkout{
    height:100svh;padding-top:100px;padding-bottom:16px;
    display:flex;flex-direction:column;gap:14px;overflow:hidden;
  }
  .pay-checkout .pay-grid{flex:1 1 auto;min-height:0}
  .pay-checkout .card{padding:22px}
  /* The cards themselves do not scroll — their contents do. The order card scrolls
     its line items, the payment card scrolls its fields, and in both cases the
     parts that must stay put (the coupon foot, the pay button) stay put. */
  .pay-checkout .pay-summary,
  .pay-checkout .pay-method{min-height:0;overflow:hidden}
  .pay-checkout .pay-items{overflow-y:auto;overscroll-behavior:contain}
  .pay-checkout .pay-form{flex:1 1 auto;min-height:0}
  .pay-checkout .pay-form-body{overflow-y:auto;overscroll-behavior:contain}
}

/* On a short laptop screen the payment column is the one thing that has to fit,
   so it tightens rather than scrolls. Every value here is a smaller version of the
   one above — no layout changes, just less air. */
@media (min-width:900px) and (max-height:820px){
  .pay-checkout{padding-top:92px}
  .pay-checkout .card{padding:18px 20px}
  .pay-checkout .pay-form,
  .pay-checkout .pay-form-body{gap:11px}
  .pay-checkout .pay-form .field input{height:42px}
  .pay-checkout .pay-tile{min-height:44px;gap:4px}
  .pay-checkout .pay-method{gap:10px}
  .pay-checkout .pay-totals{padding:15px 18px;gap:9px}
  /* Three option rows plus an expanded card form is the tallest this column ever
     gets. These trims are what make all three visible at once on a 13" laptop —
     an unpaid method the payer has to scroll to find may as well not be offered. */
  .pay-checkout .pay-options{gap:6px}
  .pay-checkout .pay-option-head{padding:9px 14px}
  .pay-checkout .pay-option-body{padding:0 14px 10px calc(14px + 18px + 10px);gap:10px}
  html[lang="ar"] .pay-checkout .pay-option-body{padding:0 calc(14px + 18px + 10px) 10px 14px}
  .pay-checkout .pay-card-fields{gap:9px}
  .pay-checkout .pay-form .field{gap:6px}
  .pay-checkout .pay-form .field input{height:38px}
  .pay-checkout .pay-form .field label{font-size:.74rem}
  .pay-checkout .pay-form-body{gap:9px}

  /* The name and email labels go visually away on a short screen — their
     placeholders already say "Your name" and "you@example.com", and the two rows
     of type they cost are the difference between Tabby being on the screen and
     being below it. Still in the DOM and still associated with their inputs, so a
     screen reader is unaffected. */
  .pay-checkout .pay-form-body>.pay-field-row .field label{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;
  }
  .pay-checkout .pay-card-fields{gap:10px}
  .pay-checkout .pay-mark{height:20px}
  .pay-checkout .pay-submit,
  .pay-checkout .wallet-btn{padding-block:13px;height:auto}
}

/* The form is a scrolling body plus a pinned action. Cheap insurance against the
   one failure this page cannot afford: a pay button nobody can find. */
.pay-form-body{display:grid;gap:14px;align-content:start}
.pay-form-action{display:grid;gap:8px}

/* ------------------------------- the header ------------------------------- */

/* Was a 3.4rem display title with a subtitle under it, which pushed the form off
   the fold on a laptop. The payer already knows they are paying — the heading
   only has to label the screen, so it is sized to do that and nothing more. */
.pay-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:2px}
.pay-title{
  font-family:var(--font-display);
  font-size:clamp(1.2rem,2.1vw,1.55rem);font-weight:700;
  letter-spacing:-.025em;line-height:1.15;
}
html[lang="ar"] .pay-title{font-family:"SF Pro AR",sans-serif;letter-spacing:0}

.pay-status .pay-head{justify-content:center;margin-bottom:20px}
.pay-status .pay-title{font-size:clamp(1.3rem,2.6vw,1.75rem)}

.pay-badge{
  display:inline-flex;align-items:center;
  padding:5px 12px;border-radius:var(--r-pill);
  border:1px solid color-mix(in srgb, var(--a4) 42%, transparent);
  background:color-mix(in srgb, var(--a4) 12%, transparent);
  color:var(--a4);font-size:.68rem;font-weight:700;
}

/* -------------------------------- the grid -------------------------------- */

/* Roughly 70/30. The wide side is the one that grows — line items, quantities, a
   discount row — while the payment column is a fixed set of controls that never
   needs more room than it has. */
.pay-grid{
  /* 60/40 rather than 70/30. The payment column holds two fields, five method
     tiles and a card form — at 30% those sat on top of each other and the whole
     card read as cramped, which is the last thing a payment form should read as. */
  display:grid;grid-template-columns:minmax(0,6fr) minmax(0,4fr);
  gap:16px;align-items:stretch;
}
@media (max-width:899px){
  .pay-grid{grid-template-columns:minmax(0,1fr)}
}
.pay-status-grid{display:grid;grid-template-columns:minmax(0,1fr);justify-items:center}

/* The cards lift on hover everywhere else on the site. Here they must not: a
   form field that shifts 2px as the pointer crosses it feels unstable, and this
   is the one page where confidence matters most. */
.pay-summary:hover,.pay-method:hover,.pay-result:hover{transform:none}
.pay-summary{display:flex;flex-direction:column;gap:20px}
.pay-method{display:flex;flex-direction:column;gap:14px}

/* ------------------------------- the order ------------------------------- */

.pay-summary-head{display:flex;align-items:center;justify-content:space-between;gap:14px}
.pay-ref{color:var(--ink-faint);font-size:.74rem}

/* Item / Qty / Amount, as a three-column table rather than a flex row — the
   quantity column is empty of controls today and holds a stepper tomorrow
   without the other two columns moving. */
.pay-items{display:flex;flex-direction:column}
.pay-items-head,.pay-item{
  display:grid;grid-template-columns:1fr 56px minmax(90px,auto);
  gap:16px;align-items:center;
}
.pay-items-head{
  padding-bottom:10px;border-bottom:1px solid var(--line);
  font-size:.66rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-faint);font-weight:700;
}
.pay-items-head>span:nth-child(2){text-align:center}
.pay-items-head>span:last-child{text-align:end}
html[lang="ar"] .pay-items-head{letter-spacing:0;text-transform:none}

.pay-item{padding:16px 0;border-bottom:1px solid var(--line)}
.pay-item:last-child{border-bottom:0}
.pay-item-name{
  display:flex;flex-direction:column;gap:4px;
  font-size:1rem;font-weight:600;line-height:1.4;color:var(--ink);
}
.pay-item-note{font-size:.75rem;font-weight:500;color:var(--ink-faint)}
.pay-item-qty{
  text-align:center;font-family:var(--font-num);font-weight:600;
  color:var(--ink-dim);font-variant-numeric:tabular-nums;
}
.pay-item-price{justify-self:end;font-size:1.02rem;font-weight:700;color:var(--ink)}

.pay-mono{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:.8rem;letter-spacing:.06em;
}

/* ------------------------------ totals + coupon --------------------------- */

/* The totals block sits at the bottom of the order card, in a tinted well so it
   reads as the conclusion of the list above rather than more of the list. The
   coupon field lives INSIDE it, next to the figures it changes — floating alone
   at the bottom of the card, it looked like an afterthought and behaved like one. */
.pay-totals{
  display:flex;flex-direction:column;gap:11px;margin-top:auto;
  padding:18px 20px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:rgba(255,255,255,.03);
}
.pay-total-line{
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  font-size:.88rem;color:var(--ink-dim);
}
.pay-total-line.saving{color:var(--a1)}
.pay-total-line.coupon{padding:3px 0}
.pay-minus{display:inline-flex;align-items:center;gap:.15em;font-weight:700}

/* The payable figure. Bigger and brighter than the lines above it, separated by a
   rule — this is the number the payer checks against the button. */
/* With the amount off the button, this is the only place the total appears — so
   it is unmissable. Deliberately larger than the reference checkouts: on a page
   with one job, the figure being agreed to should be the loudest thing on it. */
.pay-total-line.grand{
  padding-top:15px;margin-top:3px;border-top:1px solid var(--line);
  font-size:1rem;font-weight:600;color:var(--ink);
}
.pay-grand-amount{
  font-size:clamp(1.9rem,3.4vw,2.5rem);font-weight:700;
  letter-spacing:-.035em;line-height:1;color:var(--ink);
}
.pay-grand-amount .money-sign{width:.78em;height:.68em}
.pay-total-note{font-size:.74rem;line-height:1.5;color:var(--ink-faint)}
.pay-total-note strong{color:var(--ink-dim);font-weight:700}

.pay-expiry{
  display:flex;align-items:center;gap:8px;
  font-size:.78rem;color:var(--ink-faint);
}
.pay-expiry svg{
  flex:none;width:14px;height:14px;
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.pay-expiry strong{color:var(--ink-dim);font-weight:700}

/* Section headings inside the payment card, so "Payment method" and "Card
   details" are labelled rather than left to be inferred from context. */
.pay-section-label{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  font-size:.68rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-faint);font-weight:700;
}
html[lang="ar"] .pay-section-label{letter-spacing:0;text-transform:none;font-size:.78rem}
.pay-section-label.with-marks{gap:10px}

.pay-coupon-open{
  padding:0;cursor:pointer;background:none;border:0;
  font-family:var(--font-body);font-size:.82rem;color:var(--ink-dim);
  border-bottom:1px dashed var(--line-hi);transition:color .25s var(--ease)}
.pay-coupon-open:hover{color:var(--ink)}

/* Sized to the totals well it now sits in. */
.pay-coupon,.pay-coupon.applied{width:100%}
.pay-coupon-row{display:flex;gap:8px}
.pay-coupon-row input{
  flex:1;min-width:0;height:40px;padding:0 14px;
  border-radius:var(--r-sm);border:1px solid var(--line-hi);
  background:rgba(255,255,255,.04);color:var(--ink);
  font-family:var(--font-body);font-size:.9rem;letter-spacing:.08em;text-transform:uppercase;
}
.pay-coupon-row input:focus-visible{outline:2px solid var(--a2);outline-offset:1px}
.pay-coupon-apply{height:40px;padding:0 18px;font-size:.85rem;cursor:pointer;flex:none}
.pay-coupon-err{margin-top:8px;font-size:.8rem;color:#bdbdbd}

/* The applied state: a ticket stub rather than a form. */
.pay-coupon.applied{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:9px 13px;border-radius:var(--r-sm);
  border:1px solid color-mix(in srgb, var(--a1) 38%, transparent);
  background:color-mix(in srgb, var(--a1) 10%, transparent);
}
.pay-coupon-tag{display:inline-flex;align-items:center;gap:9px;color:var(--a1);font-weight:700}
.pay-coupon-tag svg{
  width:16px;height:16px;flex:none;
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.pay-coupon-x{
  padding:0;cursor:pointer;background:none;border:0;
  font-family:var(--font-body);font-size:.78rem;color:var(--ink-faint);
  transition:color .25s var(--ease);
}
.pay-coupon-x:hover:not(:disabled){color:var(--ink)}
.pay-coupon-x:disabled{cursor:default}

/* --------------------------------- money --------------------------------- */

/* An amount is a sign plus digits, assembled rather than formatted, because the
   dirham sign is drawn (see Money.tsx). Locked LTR in both languages: a figure
   is read the same way round in Arabic, and letting RTL reorder the sign and the
   digits is how "AED 4,500" becomes nonsense. */
.money{
  display:inline-flex;align-items:center;gap:.32em;white-space:nowrap;
  direction:ltr;unicode-bidi:isolate;
  font-family:var(--font-num);font-variant-numeric:tabular-nums;
}
.money-sign{width:.8em;height:.7em;flex:none;display:block}
.money-code{font-size:.72em;font-weight:600;letter-spacing:.04em;opacity:.72}
.money-digits{letter-spacing:-.015em}
.money-sm{font-size:.92em;font-weight:700}

/* ---------------------------- method selection ----------------------------
   Radio rows, one open at a time. The whole row is the hit area — a 12px radio
   dot is a target people miss, and missing it on a checkout means a payer who
   thinks the page is broken. */
.pay-options{display:flex;flex-direction:column;gap:9px}

.pay-option{
  border-radius:var(--r-sm);border:1px solid var(--line);
  background:rgba(255,255,255,.02);
  transition:border-color .25s var(--ease),background .25s var(--ease);
}
.pay-option:hover{border-color:var(--line-hi)}
.pay-option.on{
  border-color:var(--glass-border);background:rgba(255,255,255,.05);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.16);
}

.pay-option-head{
  width:100%;display:flex;align-items:center;gap:12px;
  padding:14px 16px;cursor:pointer;background:none;border:0;
  font-family:var(--font-body);text-align:start;color:var(--ink);
}
.pay-option-head:disabled{cursor:default;opacity:.6}
.pay-option-name{flex:1;min-width:0;font-size:.92rem;font-weight:600;line-height:1.3}

/* The bullet. Drawn rather than a native radio so it can match the site's
   weight — and so its checked state is a filled core rather than the OS blue. */
.pay-bullet{
  flex:none;width:18px;height:18px;border-radius:50%;
  border:1.5px solid var(--line-hi);background:transparent;
  display:grid;place-items:center;
  transition:border-color .25s var(--ease);
}
.pay-option.on .pay-bullet{border-color:var(--ink)}
.pay-option.on .pay-bullet::after{
  content:"";width:9px;height:9px;border-radius:50%;background:var(--ink);
}

.pay-option-logo{height:19px;width:auto;flex:none;display:block}
.pay-option-logo.tabby{height:22px}
.pay-option-marks{flex:none}
.pay-option-marks .pay-mark{height:19px}

/* The expanded panel, indented to the row's text so it reads as belonging to
   the option above it rather than to the group. */
.pay-option-body{
  display:grid;gap:12px;
  padding:2px 16px 16px calc(16px + 18px + 12px);
}
html[lang="ar"] .pay-option-body{padding:2px calc(16px + 18px + 12px) 16px 16px}

/* Tabby's instalment figure — the reason the method exists. */
.pay-tabby{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.pay-tabby-figure{font-size:1.3rem;font-weight:700;color:var(--ink)}
.pay-tabby-note{font-size:.82rem;color:var(--ink-dim)}

/* Card / Apple Pay / Google Pay, inside the card row. */
.pay-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.pay-tile{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  min-height:58px;padding:9px 5px;cursor:pointer;
  border-radius:var(--r-sm);border:1px solid var(--line);
  background:rgba(255,255,255,.03);color:var(--ink-dim);
  font-family:var(--font-body);
  transition:border-color .25s var(--ease),background .25s var(--ease),color .25s var(--ease);
}
.pay-tile:hover:not(:disabled),.pay-tile:focus-visible{border-color:var(--line-hi);color:var(--ink)}
.pay-tile.on{
  border-color:var(--glass-border);background:var(--glass-fill);color:var(--ink);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.28);
}
.pay-tile:disabled{opacity:.5;cursor:default}
.tile-glyph{width:20px;height:20px;flex:none;display:block}
.tile-glyph.stroked{
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.tile-name{
  font-size:.66rem;font-weight:600;line-height:1;text-align:center;
  /* Brand names are brand names in both languages — no letter-spacing games. */
  letter-spacing:.01em;
}
.tile-word{display:block;line-height:1;font-family:Helvetica,Arial,sans-serif}
.tile-word.tabby{font-size:.86rem;font-weight:700;letter-spacing:-.03em;color:#ededed}

.pay-method-note{
  display:flex;align-items:center;gap:.4em;flex-wrap:wrap;
  font-size:.85rem;line-height:1.6;color:var(--ink-dim);
}

/* -------------------------------- the form ------------------------------- */

.pay-form{display:flex;flex-direction:column;gap:14px}
.pay-field-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.pay-form .field label{font-size:.78rem}
.pay-form .field input{height:44px}

/* Card number field, with the scheme glyph tucked inside. */
.pay-input-wrap{position:relative;display:grid}
.pay-input-wrap input{padding-inline-end:44px}
.pay-input-icon{
  position:absolute;inset-inline-end:14px;top:50%;transform:translateY(-50%);
  display:grid;place-items:center;pointer-events:none;color:var(--ink-faint);
}
.pay-input-icon svg{
  width:18px;height:18px;
  stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
}

/* ---- Arabic and the card block ----
   The card block does NOT mirror. A card number, expiry and CVV are Latin
   numeral strings that read left-to-right in every locale, and an RTL container
   reverses the groups as they are typed — "4242 1111" starts reading as
   "1111 4242", which is the bug this fixes. So the fields, their order and the
   scheme glyph all keep their positions in Arabic; only the LABELS flip, which
   is the part that is actually language. */
.pay-card-fields{display:grid;gap:12px;direction:ltr}
html[lang="ar"] .pay-card-fields label{direction:rtl;text-align:right}
.pay-ltr{direction:ltr;text-align:left;unicode-bidi:isolate}
html[lang="ar"] .pay-ltr::placeholder{text-align:left}

/* Tap renders its inputs inside cross-origin iframes, so their interior cannot
   be reached from here — that isolation is what keeps the card number out of
   this page's scope. Appearance inside the frame is set through the SDK's own
   `interface` options in Checkout.tsx; this only owns the space it sits in. */
.pay-card-fields iframe{width:100%;border:0;display:block;color-scheme:dark}

/* Holds the frame's height during the dynamic import so the pay button does not
   jump down the page as the SDK arrives. */
.pay-card-skeleton{
  height:150px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:rgba(255,255,255,.03);
  animation:pay-pulse 1.6s ease-in-out infinite;
}
@keyframes pay-pulse{50%{opacity:.5}}

/* ---------------------------- accepted schemes --------------------------- */

.pay-marks{display:flex;flex-wrap:wrap;gap:6px}
.pay-mark{
  height:22px;width:auto;display:block;border-radius:4px;
  /* Full-colour on white plates, which is how the brands require them. A hair of
     transparency stops them shouting over the dark card without altering the
     artwork itself. */
  opacity:.85;transition:opacity .3s var(--ease);
}
.pay-marks:hover .pay-mark{opacity:1}

/* ------------------------------- the action ------------------------------ */

/* The site's glass pill, full width — on a checkout the primary action should
   span the form, not sit modestly at one end of it. This is the only place the
   total appears. */
.pay-submit{
  width:100%;justify-content:center;align-self:stretch;
  margin-top:2px;padding-block:16px;font-size:1rem;font-weight:700;letter-spacing:.01em;
}
.pay-submit:disabled{opacity:.6;cursor:default;transform:none;box-shadow:none}
.pay-submit-amount{
  font-weight:700;
  padding-inline-start:11px;margin-inline-start:3px;
  border-inline-start:1px solid rgba(255,255,255,.25);
  /* The glass pill has a gradient sheen in a ::before, and the dirham sign was
     sitting under it — hence a symbol you could barely see. Lifted above it, and
     drawn a size larger than body copy so the sign reads at a glance. */
  position:relative;z-index:2;color:var(--ink);
}
/* `.cta-btn svg` sets stroke:currentColor + fill:none for the arrow glyph, and it
   was catching the dirham sign too — rendering it as a hairline outline at the
   arrow's fixed 14px. That is why the symbol was barely visible on the button.
   Restated here for the sign specifically. */
.pay-submit .money-sign,
.pay-wallet-total .money-sign{
  width:.95em;height:.83em;fill:currentColor;stroke:none;
}

/* ---- wallet buttons ----
   Apple and Google both publish button specs, and both REQUIRE their own
   supplied artwork, fixed lockup order (glyph then word) and untranslated text.
   The geometry here follows those specs — white ground, because both brands
   specify the light variant on a dark background — so the official assets drop
   straight in. They are locked LTR for the same reason: "Apple Pay" is a lockup,
   not a sentence, and letting RTL reorder it would put the site out of
   compliance with the very spec it is copying. */
.wallet-btn{
  display:flex;align-items:center;justify-content:center;
  width:100%;height:48px;padding:0 18px;cursor:pointer;
  border:0;border-radius:var(--r-pill);direction:ltr;
  transition:background .25s var(--ease),transform .25s var(--ease);
}
.wallet-btn:hover:not(:disabled){transform:translateY(-1px)}
.wallet-btn:disabled{opacity:.55;cursor:default;transform:none}
.wallet-lockup{display:inline-flex;align-items:center;gap:5px}
.wallet-glyph{width:20px;height:20px;flex:none;display:block}
.wallet-word{
  font-family:-apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
  font-size:1.1rem;font-weight:600;letter-spacing:-.01em;line-height:1;
}
.wallet-btn.apple{background:#fff;color:#000}
.wallet-btn.apple:hover:not(:disabled){background:#ececec}
.wallet-btn.apple .wallet-glyph{margin-top:-3px}
.wallet-btn.google{background:#fff;color:#3c4043}
.wallet-btn.google:hover:not(:disabled){background:#ececec}
.wallet-btn.google .wallet-word{font-weight:700}
.wallet-btn.paypal{background:#ffc439;color:#003087}
.wallet-btn.paypal:hover:not(:disabled){background:#f2b52c}
.wallet-word.paypal{font-style:italic;font-weight:800;letter-spacing:-.02em}

/* Apple's and Google's buttons carry fixed text, so the figure cannot go on
   them. It sits immediately under, still part of the action block. */
.pay-wallet-total{
  text-align:center;font-size:.78rem;color:var(--ink-faint);
  display:flex;align-items:center;justify-content:center;gap:.4em;
}

.pay-spinner{
  width:16px;height:16px;flex:none;border-radius:50%;
  border:2px solid rgba(255,255,255,.25);border-top-color:var(--ink);
  animation:pay-spin .7s linear infinite;
}
.pay-spinner.dark{border-color:rgba(0,0,0,.2);border-top-color:#000}
@keyframes pay-spin{to{transform:rotate(360deg)}}

/* -------------------------------- alerts -------------------------------- */

.pay-alert{
  padding:10px 13px;border-radius:var(--r-sm);
  border:1px solid var(--line);font-size:.83rem;line-height:1.5;
}
.pay-alert.err{
  border-color:color-mix(in srgb, #9a9a9a 40%, transparent);
  background:color-mix(in srgb, #9a9a9a 9%, transparent);
  color:#bdbdbd;
}
.pay-alert.note{
  border-color:color-mix(in srgb, var(--a2) 40%, transparent);
  background:color-mix(in srgb, var(--a2) 9%, transparent);
  color:#cfcfcf;
}

/* -------------------------------- results ------------------------------- */

/* A receipt card: narrow enough to read as a document rather than a banner, tall
   enough to carry the detail rows. Not 1:1 — roughly 4:5. */
.pay-result{
  align-items:center;text-align:center;gap:0;
  width:min(540px,100%);padding:54px 44px 46px;
}

/* Solid disc, single glyph. The outlined ring it replaced read as decoration;
   a filled mark in the status colour is legible at a glance from across a room,
   which is all this element has to do. */
/* Solid white disc with a filled glyph, in every state. The tinted discs read as
   status colour-coding on a screen that only ever shows one status at a time —
   the headline already says which one it is. White is the site's own accent and
   stays out of the way. */
.pay-result-mark{
  width:72px;height:72px;margin-bottom:22px;
  display:grid;place-items:center;border-radius:50%;border:0;
  background:#fff;color:#0a0a0a;
  box-shadow:0 16px 44px -18px rgba(255,255,255,.55);
}
.pay-result-mark svg{width:36px;height:36px;fill:currentColor;stroke:none;display:block}
.pay-result-mark.ok,
.pay-result-mark.err,
.pay-result-mark.warn,
.pay-result-mark.neutral{background:#fff;color:#0a0a0a}

.pay-result-title{
  font-family:var(--font-display);
  font-size:clamp(1.2rem,2.5vw,1.55rem);font-weight:700;letter-spacing:-.025em;
  line-height:1.25;color:var(--ink);
}
html[lang="ar"] .pay-result-title{font-family:"SF Pro AR",sans-serif;letter-spacing:0}
.pay-result-body{
  margin-top:9px;max-width:42ch;
  font-size:.9rem;line-height:1.6;color:var(--ink-dim);
}

/* The receipt block: label/value rows, left-aligned inside a centred card. The
   values line up on the end edge so the amount, the reference and the date form
   a column the eye can run down. */
.pay-receipt{
  width:100%;margin-top:26px;text-align:start;
  padding:6px 20px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:rgba(255,255,255,.03);
}
.pay-receipt-row{
  display:flex;align-items:baseline;justify-content:space-between;gap:18px;
  padding:12px 0;border-bottom:1px solid var(--line);
}
.pay-receipt-row:last-child{border-bottom:0}
.pay-receipt-row dt{flex:none;font-size:.82rem;color:var(--ink-faint)}
.pay-receipt-row dd{
  margin:0;text-align:end;font-size:.88rem;font-weight:600;color:var(--ink);
  word-break:break-word;
}
.pay-receipt-row dd.saving{color:var(--a1)}
.pay-receipt-amount{font-size:1.3rem;font-weight:700}

.pay-result-actions{
  display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:26px;
}
/* .cta-btn is normally bottom-aligned inside a flex card via margin-top:auto —
   here the actions row owns the spacing instead. */
.pay-result-actions .cta-btn{margin-top:0;align-self:center}
.pay-secondary{height:auto;padding:14px 22px;font-size:.9rem}

.pay-trust{
  display:flex;align-items:center;justify-content:center;gap:7px;
  margin:14px auto 0;
  font-size:.75rem;line-height:1.5;color:var(--ink-faint);
}
.pay-trust svg{
  flex:none;width:13px;height:13px;
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.pay-trust a{color:var(--ink-dim);text-decoration:none;font-weight:600;transition:color .25s var(--ease)}
.pay-trust a:hover{color:var(--ink)}

/* ------------------------- 3-D Secure simulation ----------------------- */

/* Intentionally NOT on brand. The real screen belongs to the payer's bank, and
   dressing the simulation in this site's clothes would misrepresent what they
   are about to see. Kept generic and slightly dated on purpose. */
.bank-sim{
  min-height:100svh;padding:34px 16px;
  display:flex;flex-direction:column;align-items:center;gap:16px;
  background:#eceff3;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;
  color:#1c2430;
}
.bank-sim-note{
  width:100%;max-width:420px;padding:12px 14px;
  border-radius:6px;border:1px solid #d9a441;background:#fdf5e4;
  font-size:.8rem;line-height:1.6;color:#5c4611;
}
.bank-sim-card{
  width:100%;max-width:420px;padding:22px;
  border-radius:6px;border:1px solid #c9d1dc;background:#fff;
  box-shadow:0 1px 3px rgba(20,30,45,.12);
}
.bank-sim-head{
  display:flex;align-items:center;justify-content:space-between;
  padding-bottom:14px;margin-bottom:16px;border-bottom:1px solid #e4e8ee;
}
.bank-sim-logo{font-size:1.05rem;font-weight:700;letter-spacing:-.01em;color:#0b3d91}
.bank-sim-lock svg{
  width:19px;height:19px;
  stroke:#5b6b80;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
.bank-sim-lead{margin:0 0 16px;font-size:.88rem;line-height:1.6;color:#41505f}
.bank-sim-rows{margin:0 0 18px;font-size:.85rem}
.bank-sim-rows div{
  display:flex;justify-content:space-between;gap:14px;
  padding:7px 0;border-bottom:1px solid #eef1f5;
}
.bank-sim-rows div:last-child{border-bottom:0}
.bank-sim-rows dt{color:#67768a;margin:0}
.bank-sim-rows dd{margin:0;font-weight:600;color:#1c2430}
.bank-sim-label{display:block;margin-bottom:6px;font-size:.8rem;font-weight:600}
.bank-sim-input{
  width:100%;height:38px;padding:0 11px;margin-bottom:18px;
  border:1px solid #b9c3d0;border-radius:4px;background:#fff;
  font-size:.92rem;color:#1c2430;
}
.bank-sim-input:focus{outline:2px solid #0b3d91;outline-offset:1px}
.bank-sim-actions{display:flex;gap:10px}
.bank-sim-btn{
  flex:1;height:40px;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:4px;border:1px solid #b9c3d0;background:#f4f6f9;
  font-size:.88rem;font-weight:600;color:#31404f;text-decoration:none;cursor:pointer;
}
.bank-sim-btn.primary{background:#0b3d91;border-color:#0b3d91;color:#fff}

/* ------------------------------ responsive ----------------------------- */

@media (max-width:560px){
  .pay-page{padding-top:104px}
  /* Expiry and CVV — and name and email — stay side by side even on the
     narrowest phones: stacking them makes the form feel twice as long as it is. */
  .pay-field-row{gap:9px}
  .pay-result{padding-block:36px}
  .pay-tiles{gap:6px}
  .pay-tile{min-height:58px;padding:8px 4px}
}

@media (prefers-reduced-motion:reduce){
  .pay-spinner,.pay-card-skeleton{animation:none}
}
