/* ==========================================================================
   TARAQI — home page (step 2: hero + load sequence)
   Loads after shell.css, index.html only. Every color is a token.
   Motion here is transform/opacity only — nothing that re-lays out.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Load curtain — cream sheet over the viewport, lifted once by js/home.js
   (motion moment #1). Lives in markup so the first paint is cream even
   before the deferred scripts run. Under reduced motion it never shows.
   -------------------------------------------------------------------------- */
.load-curtain {
  position: fixed;
  inset: 0;
  z-index: 50;
  background-color: var(--taraqi-cream);
  pointer-events: none;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) { .load-curtain { display: none; } }

/* --------------------------------------------------------------------------
   Hero — cream field, typographic, full viewport
   -------------------------------------------------------------------------- */
.hero {
  background-color: var(--taraqi-cream);
  /* Fixed section height: the viewport minus the fixed bar (dvh, so mobile
     browser chrome never clips it). The content block centres inside with
     the h1 and copy sized to their own content per language — the height
     difference between the two headlines absorbs into the symmetric
     whitespace, so both languages are genuinely centred while the section
     height (what drives the footer position) never changes. */
  margin-block-start: var(--nav-h);
  min-block-size: calc(100vh - var(--nav-h));
  min-block-size: calc(100dvh - var(--nav-h));
}

/* --------------------------------------------------------------------------
   Hero footage (step 14) — the client's B162 video behind the type.

   Three layers inside .hero-media: the picture, a flat cream veil, then a
   directional cream wash that is heaviest behind the copy. The veil is what
   keeps rule 1 true now that there is footage on the screen — cream still
   holds the field, and the burgundy tagline keeps its contrast over EVERY
   frame, not just the bright ones.

   The work is split deliberately: the FLAT veil is the cream cast over the
   whole field and nothing more, so it stays light; the WASH does all the
   contrast work, and its mask now holds at full strength across the entire
   copy column before it opens. That is what lets the footage read — where
   there is no type the veil is only the flat 30%, against 72% before. The two
   stills go further and drop that flat layer to 20%; this one does not, and
   the sweep note below says why.

   Not guessed. The clip is composited under this veil and the DARKEST pixel in
   each element's own box is measured — the footage carries true black (0,0,0)
   in its shadows, so the worst case is not a bright frame, it is black behind
   the type, and these numbers hold unconditionally:
     tagline (burgundy-dark)   5.37:1
     sub-copy (ink)            6.93:1
     secondary button          5.37:1
   against a 4.5:1 floor, in both languages at 1440 and 375. Re-run that check
   with `tools/hero-veil-contrast.py` if either opacity or either mask below
   changes, and paste the new figures here.

   ---- the measured sweep, and why this hero did NOT move (2026-09-09) ----

   The check no longer samples. It used to composite every 0.5s (51 of the 758
   frames); it now reduces the WHOLE loop to one darkest-backdrop image — the
   per-pixel, per-channel minimum across all 758 — and measures against that.
   The reduction is a floor rather than a real frame, but for this footage it
   is a TIGHT floor: `--census` shows true black inside all twelve text boxes,
   covering 39%-80% of each one, so the minimum it reports is reached by an
   actual frame rather than manufactured by mixing them. The figures above are
   unchanged by the switch, which is the point — they were already pinned.

   That census is also the reason this hero keeps 0.30 + 0.60 while both stills
   moved to 0.20 + 0.65. The trade itself is available here: every text box
   sits at mask 1.0, inside the hold, so lowering the flat veil while raising
   the wash holds the composite at 72% and leaves all three figures identical
   to the second decimal. It is NOT taken, and the reason is brand rule 1, not
   contrast. The open edge is the flat veil alone, and this is footage, not a
   still that can be graded: the loop contains a night terrace passage around
   t=20s where the open edge would fall from mean luminance 0.170 to 0.123,
   bottom quartile 0.098 to 0.055. The two stills sit far brighter to begin
   with and barely move — the project dusk still 0.350 to 0.306, the about
   daylight still 0.372 to 0.332 — which is why the same edit is safe there
   and reads as a dark-luxury hero here for the seconds that passage is on
   screen. A fifth of the open edge would be at or below the luminance a black
   pixel composites to.

   The floor is the same on all three heroes, and that is a coincidence of the
   footage, not a rule: `--sweep` puts the lightest AA-clearing composite at
   66.0% (flat 0.15 + wash 0.60, 4.53:1) with 65.0% failing at 4.40:1, because
   all three bottom out at cream-over-black behind the copy. What differs is
   how MUCH black: 66.4% of this frame over the loop, against 0.071% for the
   project still and 0.131% for the about still. Headroom is per surface —
   measure it, do not carry a number across.

   The wash's alpha comes from a mask, not from a colour: every colour here is
   still the cream token, and the black/transparent in the mask gradient is an
   alpha channel, not a brand colour.
   -------------------------------------------------------------------------- */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;            /* under .hero-content (z-index 1) */
  overflow: hidden;
  pointer-events: none;
  /* The poster, at all times (Villa 13's pattern, step 6): WebP where the
     browser takes it, the JPG otherwise. The video carries no `poster`
     attribute, so this is what shows before its first frame, whenever
     autoplay is refused, and under reduced motion. */
  background-image: url("../assets/images/hero-taraqi-poster.jpg");
  background-image: image-set(
    url("../assets/images/hero-taraqi-poster.webp") type("image/webp"),
    url("../assets/images/hero-taraqi-poster.jpg") type("image/jpeg"));
  background-size: cover;
  background-position: 50% 50%;
}
.hero-video {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
/* Flat veil */
.hero-media::before,
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--taraqi-cream);
}
.hero-media::before { opacity: 0.30; }
/* Directional wash — full strength across the copy column, then opening to
   nothing so the building is genuinely legible past it. The 62% stop is not a
   round number: the Arabic tagline is the widest line on the site and its box
   ends at 61% of the hero from the copy edge, so the wash has to hold that far
   before it may start to fall away. English clears it with room. */
.hero-media::after {
  opacity: 0.60;
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 62%, transparent 82%);
          mask-image: linear-gradient(to right, #000 0%, #000 62%, transparent 82%);
}
[dir="rtl"] .hero-media::after {
  -webkit-mask-image: linear-gradient(to left, #000 0%, #000 62%, transparent 82%);
          mask-image: linear-gradient(to left, #000 0%, #000 62%, transparent 82%);
}
/* Mobile: the copy block spans the full width, so there is no far edge to open
   up along the inline axis — the wash turns 90° instead. It holds full over
   the copy band (26–82% of the hero's height, the tagline's top to below the
   buttons in both languages) and opens above and below it, where the crown of
   the building and the forecourt sit. Same 0.60 as desktop inside the band, so
   the contrast figures above hold here too.
   The [dir="rtl"] selector above is a specificity class heavier than a bare
   .hero-media::after, and a media query adds no specificity of its own — so
   this has to repeat that selector or the RTL mask would survive the query
   and leave Arabic with a directional wash it was never meant to have. */
@media (max-width: 767px) {
  .hero-media::after,
  [dir="rtl"] .hero-media::after {
    -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0.40) 0%, #000 26%, #000 82%, rgb(0 0 0 / 0.45) 100%);
            mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0.40) 0%, #000 26%, #000 82%, rgb(0 0 0 / 0.45) 100%);
  }
}
/* Reduced motion: no footage — the poster frame above, held still. The
   video's sources do not match under reduced motion (index.html), so the
   file is never requested; js/home.js still pauses the element for a
   browser that ignores `media` on a video source. */
@media (prefers-reduced-motion: reduce) {
  .hero-video { display: none; }
}

.hero-content { position: relative; z-index: 1; }

/* .mask / .mask-inner — the tagline's clip pair — moved to css/motion.css
   (shared by all four pages) in the sitewide motion step. */

/* The brand tagline at hero scale — one line per language (html[lang] hides
   the other), each in its locked face (brand sheet p.06), never re-set in body
   fonts. The h1 is sized to its own content; the section's fixed height (above)
   keeps the page below from moving. */
.hero-tagline {
  --hero-tagline-ar-size: clamp(2.5rem, 7vw, 6rem);
  --hero-tagline-ar-lh: 1.55;
  --hero-tagline-en-size: var(--display-xl-size);          /* solo headline: the shared hero h1 size, 36–56px (tokens.css) */
  margin: 0;
  font-size: 1rem;
  font-weight: 400;
}
.hero-tagline-ar {
  font-family: var(--font-ar-display);
  font-weight: 700;
  font-size: var(--hero-tagline-ar-size);
  line-height: var(--hero-tagline-ar-lh);
  color: var(--taraqi-burgundy-dark);
}
.hero-tagline-en {
  font-family: var(--font-en-display);
  font-weight: 500;
  font-style: italic;
  font-size: var(--hero-tagline-en-size);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--taraqi-burgundy-dark);
}
html[lang="en"] .hero-tagline .mask-ar,
html[lang="ar"] .hero-tagline .mask-en { display: none; }

/* The verb rises (step 9): «تَرتَقي» / "rise" is wrapped in a span inside
   the dictionary value itself (one translated string) and arrives ~200ms
   after the rest of its line, settling from slightly below on the load
   timeline (js/home.js). inline-block so it can transform — scoped to the
   hero: the same string paints in the contact band and footer, where the
   span stays a plain, unstyled word. */
.hero-tagline [data-tagline-rise] {
  display: inline-block;
  will-change: transform;
}

/* Supporting copy — wraps to its own length per language; the block
   re-centres inside the fixed-height section. */
.hero-sub { line-height: 1.625; }

/* Buttons — shared shape with the navbar CTA (rounded pill, 240ms hover) */
.btn-primary,
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 48px;             /* touch target */
  padding: 0.75rem 1.75rem;
  border-radius: 9999px;
  font-weight: 600;
  transition:
    background-color var(--dur-micro) var(--ease-rise),
    border-color var(--dur-micro) var(--ease-rise),
    color var(--dur-micro) var(--ease-rise);
}
.btn-primary {
  background-color: var(--taraqi-burgundy-dark);
  color: var(--taraqi-cream);
}
.btn-primary:hover { background-color: var(--taraqi-burgundy); }
.btn-secondary {
  background-color: transparent;
  border: 1px solid var(--taraqi-burgundy-dark);
  color: var(--taraqi-burgundy-dark);
}
.btn-secondary:hover {
  border-color: var(--taraqi-burgundy);
  color: var(--taraqi-burgundy);
}

/* Scroll cue — a 40px burgundy hairline that draws downward on a slow 2s
   loop. The ONLY looping animation on the site (blueprint §5 exception). */
.scroll-cue {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 1.5rem;
  display: flex;
  justify-content: center;
  pointer-events: none;
  z-index: 1;
}
.scroll-cue-line {
  display: block;
  inline-size: 1px;
  block-size: 40px;
  background-color: var(--taraqi-burgundy-dark);
  transform-origin: 50% 0;
  animation: cue-draw 2s var(--ease-rise) infinite;
  will-change: transform, opacity;
}
@keyframes cue-draw {
  0%   { transform: scaleY(0); opacity: 1; }
  55%  { transform: scaleY(1); opacity: 1; }
  85%  { transform: scaleY(1); opacity: 0; }
  100% { transform: scaleY(0); opacity: 0; }
}
/* tokens.css collapses animations to a single ~0ms run under reduced
   motion, which would leave the cue at its 100% (invisible) frame — so
   render the static line instead. */
@media (prefers-reduced-motion: reduce) {
  .scroll-cue-line { animation: none; transform: none; opacity: 1; }
}

/* The word masks (.rv-word) live in css/motion.css since the sitewide
   motion step — every page can split a headline now. */

/* The CTA arrow — slides toward the reading direction on hover and on
   keyboard focus; points the other way in Arabic. */
.cta-arrow {
  flex: none;
  inline-size: 1.125em;
  block-size: 1.125em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--dur-micro) var(--ease-rise);
}
html[dir="rtl"] .cta-arrow { transform: scaleX(-1); }
a:hover > .cta-arrow,
a:focus-visible > .cta-arrow { transform: translateX(4px); }
html[dir="rtl"] a:hover > .cta-arrow,
html[dir="rtl"] a:focus-visible > .cta-arrow { transform: translateX(-4px) scaleX(-1); }
@media (prefers-reduced-motion: reduce) {
  .cta-arrow { transition: none; }
}

/* --------------------------------------------------------------------------
   Projects — «مشاريعنا». The homepage shows each project from the outside
   only: one exterior picture, name, area, status, a way through to its page.

   .project-card is the reusable unit — one <article> per project, fed by its
   own key group. The picture is the site's framed figure (.about-teaser-
   figure in css/sections.css: square corners, the burgundy hairline
   rectangle offset 14px behind it), at 4:3 here. A single card runs as a
   split, picture 7 : copy 5, the copy sitting on the picture's baseline;
   from two cards up the grid is two columns and each card stacks picture
   over copy. Phones: always stacked.
   -------------------------------------------------------------------------- */
.section-projects {
  background-color: var(--taraqi-cream);
  padding-block: clamp(4rem, 10vh, 7rem) clamp(4rem, 9vh, 6rem);
  /* The nav's «مشاريعنا» / "Projects" link lands here from every page
     (index.html#projects): the section top stops under the fixed bar, not
     behind it. */
  scroll-margin-block-start: var(--nav-h);
}
.projects-grid {
  display: grid;
  gap: 3.5rem;
  margin-block-start: 2.5rem;
}
.project-card {
  display: grid;
  gap: 2rem;
  min-inline-size: 0;
  /* Cards in one row stretch to the taller one; the spare height goes under
     the copy, never into the picture's row (which would pull the outline
     away from its picture). */
  align-content: start;
}
.project-card-figure .about-teaser-media { aspect-ratio: 4 / 3; }
.project-card-figure picture {
  display: block;
  block-size: 100%;
}
/* A warm grade: the render's cold blue sky moves
   toward sand and dusk, the building's whites stay white (brand rule 2 — no
   cold blue-grey renders). Filter functions only. */
.project-card-figure img { filter: sepia(0.28) saturate(1.12) brightness(1.02); }
/* Villa 13's daylight facade is already warm — stone, timber, a pale sky —
   so it is shown as delivered, the way project-villa13.html shows it. */
.project-card-figure--photo img { filter: none; }
.project-card-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
/* Status — the section-label register, led by a short drawn line. */
.project-card-status {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--taraqi-burgundy);
}
.project-card-status::before {
  content: "";
  flex: none;
  inline-size: 1.75rem;
  block-size: 1px;
  background-color: currentColor;
}
html[lang="en"] .project-card-status {
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
/* The project's name at display size. Both names — "Villa 13", "B162" — are
   Latin in both languages, so they are set in Newsreader on the Arabic page
   too. */
.project-card-name {
  margin: 0.75rem 0 0;
  font-family: var(--font-en-display);
  font-weight: 500;
  font-size: clamp(3.25rem, 7vw, 5.5rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--taraqi-burgundy-dark);
}
.project-card-area {
  margin: 0.5rem 0 0;
  font-size: 1.125rem;
  line-height: 1.7;
  color: var(--taraqi-ink);
}
.project-card-cta {
  gap: 0.75rem;
  margin-block-start: 2rem;
}
@media (min-width: 768px) {
  .projects-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(3rem, 5vw, 5rem);
    margin-block-start: 3rem;
  }
  .project-card:only-child {
    grid-column: 1 / -1;
    grid-template-columns: 7fr 5fr;
    gap: clamp(3rem, 5vw, 5rem);
    align-items: end;
  }
  /* A status pill in the status slot (Villa 13) is taller than a plain
     status line (B162) by its own block padding. Taking that padding back
     as negative margin gives it the plain line's box, so the two labels and
     the two names in a row each share a line. */
  .project-card-body > .status-pill { margin-block: calc(var(--pill-pad-block) * -1); }
  /* the copy lands on the picture's bottom edge, not on the outline's */
  .project-card:only-child .project-card-body { padding-block-end: 14px; }
}

/* --------------------------------------------------------------------------
   Coming soon (step 4b; the title and the lineage drawings 2026-10-10) — the
   burgundy band between the projects and the commitments. Cream on deep
   burgundy: 11.2:1.

   TWO LAYOUTS, ONE MARKUP.
   Static (the default — reduced motion, or no GSAP): the three stages are a
   stacked list, each drawing complete beside its two lines of copy, numbered
   by a CSS counter, the button under them. Nothing is pinned.
   Live (.is-live, set by js/home.js §3 only when it will animate): the
   stage is one small-viewport tall and pinned; the three stages share one
   grid cell, so the cell is as tall as the tallest and nothing re-lays out
   as they swap; the counter and the three ticks (built by the script) sit
   at the stage's head and foot.

   100svh, NOT 100vh: the small viewport does not change when a phone's URL
   bar collapses, so the pinned stage never resizes mid-sequence. When the
   bar is away the viewport is taller than the stage by that bar; what shows
   in the strip below is this section's own burgundy, not the next section.
   -------------------------------------------------------------------------- */
.soon {
  background-color: var(--taraqi-wine);
  color: var(--taraqi-cream);
}
.soon-stage { padding-block-start: clamp(4rem, 9vh, 6rem); }
/* The head: the title, and (live) the counter on its baseline at the inline
   end — the row mirrors with the document's direction. */
.soon-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.375rem;
}
/* The band's title — the display face at 44–72px, one line, constant across
   the three stages. .display gives the face and the weight (Newsreader 500 /
   Aref Ruqaa 700). */
.soon-heading {
  margin: 0;
  font-size: clamp(2.75rem, 12vw, 4.5rem);
  line-height: 1;
  white-space: nowrap;
  color: var(--taraqi-cream);
}
/* The dot before it — Sand, 10px, breathing. Decorative (aria-hidden). An
   inline-block lifted to the middle of the letters rather than a flex item,
   so the title's baseline stays the text's and the counter can sit on it.
   The keyframes are attached only under no-preference: under reduced motion
   the dot is still. */
.soon-dot {
  display: inline-block;
  inline-size: 10px;
  block-size: 10px;
  margin-inline-end: 0.2em;
  border-radius: 50%;
  background-color: var(--taraqi-sand);
  vertical-align: calc(0.26em - 5px);
}
@media (prefers-reduced-motion: no-preference) {
  .soon-dot { animation: soon-pulse 1.6s ease-in-out infinite; }
}
@keyframes soon-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(0.55); opacity: 0.4; }
}
.soon-list {
  display: grid;
  gap: clamp(3rem, 8vh, 5rem);
  margin: clamp(2rem, 5vh, 3rem) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: soon;
}
.soon-item {
  display: grid;
  gap: 1.5rem;
  align-items: center;
  min-inline-size: 0;
  counter-increment: soon;
}
/* The figure is the sheet's own box, so the title-block label can sit in
   the sheet's corner. */
.soon-figure {
  position: relative;
  inline-size: 100%;
  max-inline-size: 26rem;
  min-inline-size: 0;
  margin-inline: auto;
}
.soon-svg {
  display: block;
  inline-size: 100%;
  block-size: auto;
  overflow: visible;
}
/* The elevations (tools/soon-lineage-gen.py). The drawing board's line
   (css/elevation.css) — square ends, mitred joins, a stroke that keeps its
   weight at every size — in currentColor, at three weights: 1.5px the
   outline and the street, 1px openings and slabs, 0.5px the glazing hatch,
   railings and coursing. */
.soon-svg path {
  fill: none;
  stroke: currentColor;
  stroke-linecap: butt;
  stroke-linejoin: miter;
  vector-effect: non-scaling-stroke;
  stroke-width: var(--soon-w);
}
.soon-strong { --soon-w: 1.5px; }
.soon-band   { --soon-w: 1px; }
.soon-svg .soon-line { --soon-w: 1px; }
.soon-hair   { --soon-w: 0.5px; }
/* Depth, by fill alone: what is set back is Sand at 12%, glass is Cream at
   6%, and a lit window is Sand. A mask is the band's own burgundy — it lets
   a fence stand in front of a house. */
.soon-svg .soon-recess { stroke: none; fill: var(--taraqi-sand); fill-opacity: 0.12; }
.soon-svg .soon-glaze  { stroke: none; fill: var(--taraqi-cream); fill-opacity: 0.06; }
.soon-svg .soon-warm   { stroke: none; fill: var(--taraqi-sand); fill-opacity: 0.5; }
.soon-svg .soon-mask   { stroke: none; fill: var(--taraqi-wine); }
/* The third sheet is still on the board: its building is a skeleton, its
   construction lines are dashed, and the pen is mid-stroke — the static
   page leaves it there: the strokes it has made, half of the one it is on
   (a stroke-dasharray in the markup, measured in sheet units, hence no
   non-scaling-stroke) with the tip where it stopped, and the strokes still
   to come (.soon-pen-rest) not there yet. */
.soon-svg .soon-ghost { opacity: 0.45; }
.soon-svg .soon-grid  { --soon-w: 0.5px; stroke-dasharray: 4 3; opacity: 0.6; }
.soon-svg .soon-pen   { vector-effect: none; }
.soon-pen-rest        { visibility: hidden; }
.soon-pen-tip { fill: var(--taraqi-sand); }
/* What the drawing is, for a screen reader (the SVG itself is aria-hidden). */
.soon-desc {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* The title block — the sheet's label, in its bottom corner at the inline
   end, under the street line. The section-label register, smaller. */
.soon-plate {
  position: absolute;
  inset-block-end: var(--soon-oy, 0px);
  inset-inline-end: var(--soon-ox, 0px);
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}
/* its rule rides on the line that swaps, so a label that is away takes its
   rule with it */
.soon-plate .soon-mask-inner {
  padding-block-start: 0.3rem;
  border-block-start: 1px solid var(--taraqi-line-onDark);
}
html[lang="en"] .soon-plate {
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.soon-copy::before {
  content: "0" counter(soon);
  display: block;
  margin-block-end: 0.75rem;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  opacity: 0.7;
}
/* One level under the band's title: 32–40px. */
.soon-title {
  margin: 0;
  font-size: clamp(2rem, 4.2vw, 2.5rem);
  line-height: 1.15;
  color: var(--taraqi-cream);
}
html[lang="ar"] .soon-title { line-height: 1.5; }
p.soon-line {
  margin: 1rem 0 0;
  max-inline-size: 30ch;
  font-size: 1.125rem;
  line-height: 1.9;
}
/* The copy's clip boxes. Padding out, margin back in (css/motion.css's
   .mask idiom) so Aref Ruqaa's marks are never cut at rest. */
.soon-mask {
  display: block;
  overflow: hidden;
  padding-block: 0.3em;
  margin-block: -0.3em;
}
.soon-mask-inner { display: block; }
.soon-exit { padding-block: clamp(2.5rem, 6vh, 4rem) clamp(4rem, 9vh, 6rem); }
@media (min-width: 768px) {
  .soon-item { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: clamp(2rem, 5vw, 5rem); }
  .soon-copy { grid-column: 1; grid-row: 1; }
  .soon-figure { grid-column: 2; grid-row: 1; }
  p.soon-line { font-size: 1.25rem; }
}

/* --- Live. --- */
.soon.is-live .soon-stage {
  block-size: 100vh;
  block-size: 100svh;
  padding-block: var(--nav-h) 0;   /* the fixed bar sits over the pinned stage */
}
.soon.is-live .soon-inner {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: clamp(0.75rem, 2.5vh, 1.5rem);
  block-size: 100%;
  padding-block: clamp(1.25rem, 3.5vh, 2.5rem);
}
.soon.is-live .soon-list { gap: 0; margin: 0; min-block-size: 0; }
.soon.is-live .soon-item {
  grid-area: 1 / 1;
  grid-template-rows: minmax(0, 1fr) auto;   /* the drawing above the copy */
  min-block-size: 0;
}
.soon.is-live .soon-copy::before { content: none; }
.soon.is-live .soon-figure { max-inline-size: none; block-size: 100%; margin-inline: 0; }
.soon.is-live .soon-svg {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
}
/* pathLength="1" on every drawn path: one dash is the whole line, so the
   script draws and un-draws it with stroke-dashoffset 1 ↔ 0. Only here — the
   static page never dashes a line it has finished.
   NO non-scaling-stroke WHILE DASHED. With it the browser measures the dash
   in screen pixels but the path in sheet units, so on a sheet drawn at any
   scale but 1 a "whole line" dash is the wrong length and a hidden line
   leaves pieces behind. The weight is held constant the other way: the
   script writes --soon-k (sheet units per pixel) on the svg and the stroke
   is the same px weight multiplied back by it. */
.soon.is-live .soon-svg path {
  vector-effect: none;
  stroke-width: calc(var(--soon-w) * var(--soon-k, 1));
}
.soon.is-live .soon-svg :is([data-soon-ground], [data-soon-morph], [data-soon-draw], [data-soon-pen]) {
  stroke-dasharray: 1;
}
/* While the script morphs, the first sheet's outline, slabs and street line
   are the only ones: they become each building in turn (js/home.js §3). */
.soon.is-morph .soon-item + .soon-item :is([data-soon-ground], [data-soon-morph]) { visibility: hidden; }
.soon.is-morph .soon-ghost { opacity: 1; }   /* the script thins the lineage itself */
.soon.is-live .soon-pen-rest { visibility: visible; }   /* the script draws every stroke */
/* Phones: the sticky register bar is on screen for the whole pin, so the
   stage's foot is padded by the bar's height (--cta-bar-h, css/shell.css)
   and the copy and the ticks end above it. */
@media (max-width: 767px) {
  .soon.is-live .soon-stage { padding-block-end: var(--cta-bar-h, 0px); }
  .soon.is-live .soon-inner { padding-block-end: 1.25rem; }
  /* the three blocks of copy are one height (--soon-copy-h, the tallest,
     written by the script), so the three figures above them are one size
     and the sheets lie exactly on one another */
  .soon.is-live .soon-copy { min-block-size: var(--soon-copy-h, 0px); }
}
@media (min-width: 768px) {
  .soon.is-live .soon-item { grid-template-rows: minmax(0, 1fr); }
  .soon.is-live .soon-figure { max-block-size: 34rem; align-self: center; }
}
/* The counter — "01 / 03", small, on the title's baseline. A number, so it
   stays left-to-right in both languages (.ltr-num); its place at the inline
   end of the head mirrors. The current numeral swaps through a mask, the
   three stacked in one cell. */
.soon-counter {
  display: inline-flex;
  flex: none;
  align-items: baseline;
  gap: 0.3em;
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 768px) {
  .soon-counter { gap: 0.5em; font-size: 0.875rem; letter-spacing: 0.12em; }
}
.soon-count {
  display: inline-grid;
  overflow: hidden;
}
.soon-count > span { grid-area: 1 / 1; }
.soon-counter-total { opacity: 0.6; }
/* Three ticks, filled from the inline start in reading order — so the bar
   runs right to left in Arabic with the row's own direction, and each fill
   grows from its inline-start end (transform-origin has no logical keyword,
   hence the [dir] pair, as .values-rule does). */
.soon-progress { display: flex; align-items: center; gap: 0.5rem; }
.soon-tick {
  position: relative;
  inline-size: clamp(2.5rem, 7vw, 4.5rem);
  block-size: 2px;
}
.soon-tick::before,
.soon-tick > i {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--taraqi-cream);
}
.soon-tick::before { opacity: 0.25; }
.soon-tick > i { transform-origin: 0 50%; }
html[dir="rtl"] .soon-tick > i { transform-origin: 100% 50%; }
