/* ============================================================
   السيف — Atmosphere layer

   The brand is salted fish pulled out of the sea and cured by
   hand on a night counter. So the page is not a flat navy
   rectangle: it is water. Light rays fall through it, the deep
   gets darker the further you scroll, and a fine grain of salt
   sits over everything.

   Nothing in this file carries meaning on its own — every layer
   is decorative, pointer-events:none, and safe to fail. Motion
   is transform/opacity only, and base.css already flattens all
   of it under prefers-reduced-motion.
   ============================================================ */

/* ------------------------------------------------ the deep

   The gradient lives on <body>, not on a fixed layer, so it
   stretches the whole document: scrolling down genuinely
   descends into darker water instead of panning a static wash. */

/* Scoped to pages that actually carry the atmosphere layer. The admin
   view includes head.html but not the public header, so it keeps the flat
   --sea from base.css — and any browser without :has() falls back to that
   same flat navy, which is exactly the previous design. */
body:has(.ambient) {
  background: linear-gradient(
    180deg,
    #10283e 0%,
    #0b1c2c 18%,
    var(--sea) 42%,
    #071220 74%,
    #040b13 100%
  );
  background-attachment: scroll;
}

/* Content rides above the atmosphere. The header and bottom nav
   already carry z-index 100; these just need to clear layer 0. */
main,
.footer,
.announce {
  position: relative;
  z-index: 1;
}

.ambient {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
}

.ambient > * {
  position: absolute;
  inset: 0;
}

/* ---------------------------------------------------- aurora

   Four soft bodies of colour drifting at different periods so
   the loop never visibly repeats. Blurred past recognition —
   they read as light in water, not as circles. */

.ambient__aurora {
  filter: blur(90px);
  opacity: 0.5;
}

.ambient__aurora i {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform;
}

/* The lamp over the counter — warm, high, and the only amber in
   the atmosphere, so the hero's CTA still owns the colour. */
.ambient__aurora i:nth-child(1) {
  width: 46vw;
  height: 46vw;
  min-width: 320px;
  min-height: 320px;
  top: -14vh;
  inset-inline-end: -6vw;
  background: radial-gradient(circle, rgba(245, 166, 35, 0.5), transparent 68%);
  animation: drift-a 26s var(--ease) infinite alternate;
}

.ambient__aurora i:nth-child(2) {
  width: 58vw;
  height: 58vw;
  min-width: 380px;
  min-height: 380px;
  top: 6vh;
  inset-inline-start: -18vw;
  background: radial-gradient(circle, rgba(61, 165, 232, 0.55), transparent 68%);
  animation: drift-b 34s var(--ease) infinite alternate;
}

.ambient__aurora i:nth-child(3) {
  width: 52vw;
  height: 52vw;
  min-width: 340px;
  min-height: 340px;
  bottom: -20vh;
  inset-inline-start: 22vw;
  background: radial-gradient(circle, rgba(34, 193, 206, 0.42), transparent 70%);
  animation: drift-c 42s var(--ease) infinite alternate;
}

.ambient__aurora i:nth-child(4) {
  width: 34vw;
  height: 34vw;
  min-width: 260px;
  min-height: 260px;
  bottom: 8vh;
  inset-inline-end: -8vw;
  background: radial-gradient(circle, rgba(31, 124, 188, 0.5), transparent 70%);
  animation: drift-d 30s var(--ease) infinite alternate;
}

@keyframes drift-a {
  to { transform: translate3d(-7vw, 9vh, 0) scale(1.18); }
}
@keyframes drift-b {
  to { transform: translate3d(9vw, -6vh, 0) scale(0.86); }
}
@keyframes drift-c {
  to { transform: translate3d(-11vw, -8vh, 0) scale(1.14); }
}
@keyframes drift-d {
  to { transform: translate3d(6vw, 7vh, 0) scale(1.22); }
}

/* -------------------------------------------------- caustics

   Two sets of thin lines crossing at different angles. Blurred
   and drifting, the moiré between them reads the way sunlight
   reads on the floor of a shallow pool. Masked to the top of
   the viewport — light does not reach the bottom of the sea. */

.ambient__caustics {
  background-image:
    repeating-linear-gradient(
      114deg,
      transparent 0 26px,
      rgba(111, 228, 237, 0.055) 26px 28px
    ),
    repeating-linear-gradient(
      68deg,
      transparent 0 34px,
      rgba(61, 165, 232, 0.045) 34px 36px
    );
  filter: blur(2px);
  -webkit-mask-image: radial-gradient(125% 72% at 50% -6%, #000 0%, transparent 72%);
  mask-image: radial-gradient(125% 72% at 50% -6%, #000 0%, transparent 72%);
  animation: caustics 28s linear infinite;
  will-change: transform;
}

@keyframes caustics {
  to { transform: translate3d(-62px, 72px, 0); }
}

/* ------------------------------------------------------ salt

   feTurbulence grain. On a salted-fish brand the noise is not a
   texture cliché — it is the product. Kept under 0.09 opacity
   so it grains the gradient without ever reading as dirt. */

.ambient__salt {
  inset: -140px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23s)'/%3E%3C/svg%3E");
  opacity: 0.075;
  mix-blend-mode: overlay;
  animation: salt-shift 1.1s steps(5) infinite;
}

@keyframes salt-shift {
  0%   { transform: translate3d(0, 0, 0); }
  20%  { transform: translate3d(-3%, 2%, 0); }
  40%  { transform: translate3d(2%, -3%, 0); }
  60%  { transform: translate3d(-2%, -2%, 0); }
  80%  { transform: translate3d(3%, 3%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* Full-screen blended grain is the one layer that costs real
   battery on a phone. Freeze it there; the texture still lands. */
@media (pointer: coarse) {
  .ambient__salt { animation: none; }
  .ambient__aurora { filter: blur(70px); }
}

/* --------------------------------------------------- vignette */

.ambient__vignette {
  background: radial-gradient(
    132% 100% at 50% 0%,
    transparent 38%,
    rgba(2, 8, 14, 0.62) 100%
  );
}

/* -------------------------------------------- scroll tide line

   Reads as the header's own underline filling with amber as the
   page is consumed, rather than a bar bolted to the viewport. */

.scroll-tide {
  position: fixed;
  inset-inline: 0;
  top: calc(var(--header-h) - 2px);
  height: 2px;
  z-index: 101;
  pointer-events: none;
  background: transparent;
}

.scroll-tide i {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  background: linear-gradient(90deg, var(--lagoon), var(--tide), var(--amber));
  box-shadow: 0 0 12px rgba(245, 166, 35, 0.5);
  transition: width 0.1s linear;
}

/* ==============================================================
   HERO
   ============================================================== */

.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* A lamp that follows the pointer. --mx/--my are written by
   home-effects.js; without JS the gradient simply sits centred
   at its default and never animates. */
.hero__spot {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    620px circle at var(--mx, 50%) var(--my, 30%),
    rgba(245, 166, 35, 0.13),
    rgba(61, 165, 232, 0.07) 42%,
    transparent 66%
  );
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}

.hero.is-lit .hero__spot { opacity: 1; }

.hero__inner { perspective: 1200px; }

/* ------------------------------------------------ status pill

   The single highest-value element on the page for a food shop:
   whether you can order right now. Filled by home-effects.js
   from Africa/Cairo time, and hidden until it knows the answer
   so it can never assert the wrong one. */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.32rem 0.85rem 0.32rem 0.7rem;
  margin-bottom: var(--sp-3);
  border-radius: var(--radius-pill);
  background: var(--sea-2);
  border: 1px solid var(--line-soft);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  backdrop-filter: blur(8px);
}

.status-pill b { color: var(--ink); font-weight: 700; }
.status-pill span { color: var(--ink-faint); font-weight: 500; }

.status-pill i {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-faint);
  flex: none;
}

.status-pill i::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: inherit;
  animation: ping 2s var(--ease) infinite;
}

.status-pill.is-open { border-color: var(--success-mist); background: var(--success-mist); }
.status-pill.is-open i { background: var(--success); }
.status-pill.is-shut { border-color: var(--line-soft); }
.status-pill.is-shut i { background: var(--warning); }

@keyframes ping {
  0%   { transform: scale(1); opacity: 0.7; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* ---------------------------------------------- kinetic title

   Split by LINE, never by letter — Arabic is cursive and
   per-character spans would break the joining forms. */

/* The clip box is padded below the baseline so the hand-drawn stroke,
   which hangs outside the line box, is not sliced off by the reveal. The
   negative margin gives the space straight back to the layout. */
.hero__title .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.3em;
  margin-bottom: -0.16em;
}

.hero__title .line > * {
  display: inline-block;
  animation: line-rise 0.9s var(--ease) both;
}

.hero__title .line:nth-child(1) > * { animation-delay: 0.05s; }
.hero__title .line:nth-child(2) > * { animation-delay: 0.18s; }

@keyframes line-rise {
  from { transform: translateY(118%) rotate(2deg); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* The wordmark is hand-lettered and the fish is hand-salted, so
   the emphasis gets a stroke that draws itself rather than a
   flat highlight. overflow:visible keeps the cap ends. */
.hero__title em {
  position: relative;
  font-style: normal;
  color: var(--amber-bright);
}

/* base.css caps every svg at max-width:100%; this one is inset past both
   edges of the word, so it has to opt out or it gets squeezed. */
.hero__title .stroke {
  position: absolute;
  inset-inline: -0.06em;
  bottom: -0.18em;
  width: auto;
  max-width: none;
  height: 0.34em;
  overflow: visible;
}

.hero__title .stroke path {
  fill: none;
  stroke: var(--amber);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 340;
  stroke-dashoffset: 340;
  animation: draw-stroke 1.2s var(--ease) 0.8s forwards;
  filter: drop-shadow(0 2px 8px rgba(245, 166, 35, 0.45));
}

@keyframes draw-stroke {
  to { stroke-dashoffset: 0; }
}

.hero__lead,
.hero__actions,
.hero__note,
.hero__stats {
  animation: fade-up 0.8s var(--ease) both;
}

.hero__lead    { animation-delay: 0.34s; }
.hero__actions { animation-delay: 0.44s; }
.hero__stats   { animation-delay: 0.54s; }
.hero__note    { animation-delay: 0.62s; }

@keyframes fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------- primary CTA lift

   A sheen that crosses the button once on hover. The button is
   the only amber block in the viewport; this keeps it feeling
   pressable without adding another colour. */

.hero__actions .btn { position: relative; overflow: hidden; }

.hero__actions .btn--primary {
  box-shadow: var(--shadow-brand);
  transition: transform 0.25s var(--ease-bounce), box-shadow 0.25s var(--ease);
}

.hero__actions .btn--primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 34px rgba(245, 166, 35, 0.4);
}

.hero__actions .btn::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 45%;
  inset-inline-start: -60%;
  background: linear-gradient(
    100deg,
    transparent,
    rgba(255, 255, 255, 0.38),
    transparent
  );
  transform: skewX(-18deg);
  pointer-events: none;
}

.hero__actions .btn:hover::before { animation: sheen 0.7s var(--ease); }

@keyframes sheen {
  to { inset-inline-start: 130%; }
}

/* --------------------------------------------------- hero stats

   Counts pulled from the live menu and reviews payload — real
   numbers only. Renders empty and stays empty if the API is
   down, so the page never invents proof it does not have. */

.hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-5);
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line-soft);
}

.hero__stats:empty { display: none; }

.hero__stats li { min-width: 0; }

.hero__stats b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 800;
  line-height: 1.15;
  color: var(--amber-bright);
  font-variant-numeric: tabular-nums;
}

.hero__stats span {
  display: block;
  font-size: var(--text-xs);
  color: var(--ink-faint);
  letter-spacing: 0.02em;
}

/* ----------------------------------------------- hero artwork

   --py comes from scroll, --rx/--ry from the pointer. Keeping
   them in separate custom properties lets both inputs drive one
   transform without either overwriting the other. */

.hero__art {
  transform:
    translate3d(0, var(--py, 0px), 0)
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg));
  transform-style: preserve-3d;
  transition: transform 0.5s var(--ease);
  will-change: transform;
}

.hero__art img {
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--line-soft);
  animation: float-in 0.9s var(--ease) both;
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
}

.hero__art img:nth-child(1) { animation-delay: 0.3s; transform: translateZ(30px); }
.hero__art img:nth-child(2) { animation-delay: 0.42s; transform: translateZ(16px); }
.hero__art img:nth-child(3) { animation-delay: 0.54s; transform: translateZ(48px); }

.hero__art img:hover {
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--tide-mist);
}

@keyframes float-in {
  from { opacity: 0; transform: translateY(26px) scale(0.94); }
}

/* ----------------------------------------------------- tide

   The waterline closing the hero. js/tide.js paints a Gerstner
   surface onto the canvas; the container keeps a gradient of
   its own so the band still reads as water when the canvas is
   blank. Full-bleed, and the layer fills fade out downward, so
   there is no seam where the water meets the page gradient. */

/* Roughly the top half is deliberately empty air: a canvas cannot draw
   outside itself, and the fish needs somewhere to jump to. WATER_TOP in
   tide.js is the matching constant — keep the two in step. The negative
   top margin lets that air overlap the hero rather than pad it out. */
.tide {
  position: relative;
  width: 100%;
  height: clamp(168px, 19vw, 268px);
  margin-top: calc(var(--sp-5) * -1);
  margin-bottom: calc(var(--sp-6) * -1);
  background: linear-gradient(
    180deg,
    transparent 0%,
    transparent 50%,
    rgba(19, 42, 64, 0.26) 66%,
    rgba(7, 18, 32, 0.42) 100%
  );
  pointer-events: none;
}

.tide__surface {
  display: block;
  width: 100%;
  height: 100%;
}

/* ==============================================================
   SEASON RIBBON

   Fesikh is the food of شم النسيم — the single day the whole
   category lives or dies on. The ribbon computes the date from
   Coptic Easter and only unhides itself inside the ordering
   window, so the page is never counting down to something nine
   months away.
   ============================================================== */

.season {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius-lg);
  background:
    linear-gradient(120deg, rgba(245, 166, 35, 0.16), rgba(34, 193, 206, 0.1)),
    var(--surface);
  border: 1px solid var(--amber-mist);
  position: relative;
  overflow: hidden;
}

.season::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 30%,
    rgba(255, 214, 138, 0.14) 50%,
    transparent 70%
  );
  transform: translateX(-100%);
  animation: season-sweep 5.5s var(--ease) infinite;
  pointer-events: none;
}

@keyframes season-sweep {
  0%, 55% { transform: translateX(-100%); }
  100%    { transform: translateX(100%); }
}

.season__mark {
  font-size: 1.7rem;
  line-height: 1;
  flex: none;
  animation: sway 3.4s var(--ease) infinite alternate;
}

@keyframes sway {
  from { transform: rotate(-8deg); }
  to   { transform: rotate(8deg); }
}

.season__body { flex: 1 1 16rem; min-width: 0; }

.season__body strong {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--ink);
}

.season__body span {
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.season__count {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex: none;
  padding-inline: var(--sp-4);
  border-inline-start: 1px solid var(--line-soft);
}

.season__count b {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 800;
  line-height: 1;
  color: var(--amber-bright);
  font-variant-numeric: tabular-nums;
}

.season__count em {
  font-style: normal;
  font-size: var(--text-xs);
  color: var(--ink-faint);
}

@media (max-width: 560px) {
  .season__count {
    border-inline-start: 0;
    padding-inline: 0;
  }
}

/* ==============================================================
   TRUST TICKER

   The four claims the shop actually competes on, moving slowly
   enough to read. Written twice in the markup rather than
   cloned in JS so the loop survives with scripting off.
   ============================================================== */

.ticker {
  position: relative;
  overflow: hidden;
  padding-block: var(--sp-3);
  border-block: 1px solid var(--line-soft);
  background: rgba(19, 42, 64, 0.35);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker-roll 38s linear infinite;
  will-change: transform;
}

.ticker:hover .ticker__track { animation-play-state: paused; }

/* Each copy is its own box, so 50% of the track is exactly one copy
   and the loop closes with no gap arithmetic to get wrong. `100vw`
   keeps a copy at least as wide as the band, so the track still
   covers the screen at the end of the cycle on wide displays. */
.ticker__seq {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: space-around;
  min-width: 100vw;
}

/* Positive X because the page is RTL: the track hangs off to the
   left, so it travels right to pull the next copy into view. */
@keyframes ticker-roll {
  to { transform: translate3d(50%, 0, 0); }
}

.ticker__track b {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}

.ticker__track i {
  font-style: normal;
  font-size: 0.7rem;
  color: var(--amber);
  opacity: 0.8;
  /* Carries the spacing instead of a track `gap`, so the seam between
     two copies is spaced exactly like every other join. */
  padding-inline: var(--sp-5);
}

/* ==============================================================
   CARD SPOTLIGHT

   One delegated pointermove on <main> writes --mx/--my onto the
   hovered card. Cheap, and it degrades to a plain hover border
   with no JS at all.
   ============================================================== */

.cat-tile,
.review,
.dish {
  position: relative;
  transition:
    transform 0.3s var(--ease),
    border-color 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

.cat-tile::after,
.review::after,
.dish::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    240px circle at var(--mx, 50%) var(--my, 50%),
    rgba(245, 166, 35, 0.14),
    transparent 68%
  );
  opacity: 0;
  transition: opacity 0.35s var(--ease);
  z-index: 2;
}

.cat-tile:hover::after,
.review:hover::after,
.dish:hover::after { opacity: 1; }

@media (hover: hover) {
  .cat-tile:hover,
  .dish:hover {
    transform: translateY(-4px);
    border-color: var(--amber-mist);
    box-shadow: var(--shadow-lg);
  }

  .review:hover {
    border-color: var(--tide-mist);
    box-shadow: var(--shadow);
  }
}

/* The photo breathes inside a tile that does not move — the
   frame stays put, the food comes forward. */
.cat-tile img,
.dish__photo img {
  transition: transform 0.55s var(--ease);
}

@media (hover: hover) {
  .cat-tile:hover img,
  .dish:hover .dish__photo img { transform: scale(1.07); }
}

/* ==============================================================
   SECTION HEADINGS

   A short amber rule leading the eyebrow, drawn in on reveal, so
   each section announces itself instead of just starting.
   ============================================================== */

.section-head .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

.section-head .eyebrow::before {
  content: '';
  width: 26px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--amber), transparent);
}

/* ==============================================================
   STAGGERED REVEAL

   ui.js already toggles .is-visible; this only adds the per-item
   delay and a slightly richer entrance for home-page grids.
   ============================================================== */

.js-reveal .reveal { transform: translateY(18px) scale(0.99); }
.js-reveal .reveal.is-visible { transform: none; }

.js-reveal .reveal-group > * {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
}

.js-reveal .reveal-group.is-visible > * {
  opacity: 1;
  transform: none;
}

.js-reveal .reveal-group.is-visible > *:nth-child(1) { transition-delay: 0.04s; }
.js-reveal .reveal-group.is-visible > *:nth-child(2) { transition-delay: 0.1s; }
.js-reveal .reveal-group.is-visible > *:nth-child(3) { transition-delay: 0.16s; }
.js-reveal .reveal-group.is-visible > *:nth-child(4) { transition-delay: 0.22s; }
.js-reveal .reveal-group.is-visible > *:nth-child(5) { transition-delay: 0.28s; }
.js-reveal .reveal-group.is-visible > *:nth-child(6) { transition-delay: 0.34s; }

/* ==============================================================
   MOTION OPT-OUT

   base.css already collapses durations globally. These layers
   are purely decorative, so under a reduced-motion preference
   the drifting ones are removed outright rather than left
   frozen mid-drift.
   ============================================================== */

@media (prefers-reduced-motion: reduce) {
  .ambient__caustics,
  .ambient__salt,
  .season::before { display: none; }

  .ambient__aurora i { animation: none; }
  .ticker__track { animation: none; }
  .hero__art { transition: none; }
}
