/* ==========================================================================
   MANGO — page sections
   ========================================================================== */

/* ============================ HERO ======================================
   The hero has NO dark panel — the ground stays light all the way across.
   (Client direction: white and minimal; the supplied 3D renders are white
   objects on a near-white plate, so a light ground is also what they need.)

   SIDES: the copy sits on the RTL reading start (the right) and the render
   sits on the left. Logical properties are deliberately NOT used for the
   render — `inset-inline-start` resolves to the RIGHT in an RTL document,
   which mirrors the composition the wrong way. These are physical `left`
   values on purpose.

   THE RENDER: white objects on a near-white plate. `multiply` drops that
   plate out against bg/page, so the objects read and the frame disappears.
   ====================================================================== */
/* `isolation:isolate` gives the multiply blend a group to composite in, but
   that group must actually PAINT the ground colour — otherwise the backdrop
   is transparent and the raw white plate shows through as a white box.     */
.hero{position:relative;padding-block:var(--space-16) var(--section-pad);overflow:hidden;
  isolation:isolate;background:var(--page-bg)}

/* A giant, near-invisible MANGO wordmark sits under the render. */
.hero__mark{
  position:absolute;z-index:1;
  left:2%;top:47%;transform:translateY(-50%);
  font-family:var(--font-en-display);
  font-size:clamp(96px,13vw,200px);
  line-height:.8;letter-spacing:-.03em;
  color:var(--ink-300);opacity:.19;      /* near-invisible, not a grey slab */
  pointer-events:none;user-select:none;white-space:nowrap;direction:ltr;
}

/* PORTFOLIO is nearly twice as long as MANGO, so the archive mark is set
   smaller — same optical width, still clear of the frame.
   It stays on the left, over the render. .clients__crop paints an opaque
   page-bg ground, so a mark left below it is invisible; lifting it above the
   inner grid and multiplying it in keeps it readable as a watermark instead of
   a flat grey slab over the video. */
.hero__mark--pf{
  z-index:4;                 /* .pf-open__inner is 3 */
  mix-blend-mode:multiply;
  font-size:clamp(64px,8.5vw,132px);
}

/* Flush with .wrap's content edge instead of 2% of the viewport, so the mark
   lines up with the column beneath it at every width.
   The home mark was left on the old left:2% and drifted outside the column —
   46px out at 1440, and once --wrap-pad drops to 40 the gap grew to 105. */
.hero__mark,
.hero__mark--pf,
.hero__mark--svc{left:calc((100% - min(100%, 1440px)) / 2 + var(--wrap-pad))}

/* A service's featured image, shown opposite its heading. It keeps its own
   aspect ratio rather than going through .clients__crop: that box is 4:3 and
   this artwork is portrait, so cover-cropping it would cut the composition in
   half. Height-capped so a tall image cannot outgrow the hero. */
.svc-art{
  position:absolute;z-index:2;
  left:calc((100% - min(100%, 1440px)) / 2 + var(--wrap-pad));
  top:50%;transform:translateY(-50%);
  /* Bumped from min(37%,400px): the copy column is capped at 600px and sits
     on the right, so at 1440 there is ~780px of free ground on the left. 560
     fills it without crowding the lede, and the 1200px source still covers it
     at 2x. */
  /* Width is the cap, but height is the one that actually bit: the art is
     absolutely positioned and vertically centred, so a portrait render simply
     grew past the hero — the UI/UX one ran 517px tall in a shorter band and
     slid under the sticky header. The box now hugs the image and the image is
     height-capped, so a tall source shrinks instead of escaping. */
  width:auto;
  /* Client-set, bumped from min(48%,560px). Raising this alone does nothing
     for a portrait source — the height cap below is what actually binds — so
     both moved together, and .svc-hero grew to keep the art inside it. */
  max-width:min(56%,660px);
  pointer-events:none;
  /* No multiply here. The other renders sit on near-white plates that multiply
     away against the page; this one's ground is a genuine grey, so multiplying
     it only painted a darker rectangle. Feathering the edge instead lets the
     plate dissolve into the page the way the rest of the hero art does.

     The radius stays at ~50% of the box for the reason noted on
     .clients__render: the gradient's 100% stop sits at the ellipse edge, so
     anything larger is still opaque where the box actually ends. */
  -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 52%,transparent 100%);
          mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 52%,transparent 100%);
}
.svc-art img{
  width:auto;height:auto;display:block;
  max-width:100%;
  max-height:min(56vh,520px);
}
/* The hero must never be shorter than the art it centres, or the art spills
   into the section below. */
/* 540 is the shared floor: seven templates use .svc-hero and only the single
   service page carries art. Reserving the taller band for all of them left
   100px of dead space above the cards on the archive and every plain page. */
.svc-hero{min-height:540px}
.svc-hero--art{min-height:640px}
/* The services archive carries neither art nor the ghost wordmark (the other
   .svc-hero pages carry one or the other, which is what the floor is for), so
   its hero sizes to its own copy instead of reserving an empty band. */
.svc-hero--bare{min-height:0}
@media (max-width:960px){.svc-hero{min-height:0}}

/* Service-page image grid. The live pages mix tall illustrations with wide
   screenshots, so the row height is left to the content and only the column
   count is fixed — cropping them to a uniform tile would cut the screenshots
   in half. */
.svc-gallery{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:var(--space-6);
  align-items:start;
}
.svc-gallery__item{margin:0}

/* ============================ TESTIMONIALS ==============================
   A wall of quote cards, not a carousel. Column count comes from the template
   (--twall-cols) so the grid never leaves an empty column when there are fewer
   quotes than columns. Cards keep their natural height, so a short quote makes
   a short card — that unevenness is the look, not a bug.                    */
/* Static fallback layout (the few-quotes case). Multi-column rather than grid:
   a grid lays cards out in rows and strands a lone card in a second row. */
.twall{
  columns:var(--twall-cols,3);
  column-gap:var(--space-6);
  margin-top:var(--space-12);
}

/* --- the marquee wall -----------------------------------------------------
   Columns scroll vertically in alternating directions, measured off the
   reference at roughly 80px/s, and the ends fade rather than being cut. Each
   column prints its cards twice, so translating the run by exactly -50% lands
   on the identical frame and the loop is seamless with no JavaScript. */
.twall--marquee{
  columns:auto;                    /* undo the static fallback */
  display:grid;
  grid-template-columns:repeat(var(--twall-cols,3),1fr);
  gap:var(--space-6);
  height:560px;
  overflow:hidden;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 90px,#000 calc(100% - 90px),transparent 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 90px,#000 calc(100% - 90px),transparent 100%);
}
.single-post section.hero.svc-hero.hero--centered {
    min-height: auto;
}
.single-post p.lede {
    text-align: center;
    max-width: 100%;
}
.social {
    display: none!important;
}
.twall__col{overflow:hidden}
.twall__run{
  display:flex;flex-direction:column;gap:var(--space-6);
  will-change:transform;
  animation:twall-up var(--twall-dur,28s) linear infinite;
}
/* Starting at -50% and running to 0 is the same loop read backwards, which is
   what makes the neighbouring column travel the other way. */
.twall__run--down{animation-name:twall-down}
@keyframes twall-up   {from{transform:translateY(0)}      to{transform:translateY(-50%)}}
@keyframes twall-down {from{transform:translateY(-50%)}   to{transform:translateY(0)}}

/* Stop while the pointer is over the wall, so a quote can actually be read. */
.twall--marquee:hover .twall__run{animation-play-state:paused}

/* Marquee off: the wall becomes a plain stack of every quote, full height, no
   loop. Shared by reduced-motion and by phones, where a scrolling lane cannot
   work — the columns stack, so each lane gets a fraction of the height and
   chops its cards in half. */
@media (prefers-reduced-motion:reduce){
  .twall--marquee{display:block;height:auto;overflow:visible;-webkit-mask-image:none;mask-image:none}
  .twall--marquee .twall__col{overflow:visible}
  .twall__run{animation:none;display:block}
  /* With the loop off, the duplicate pass is just repetition — hide it. */
  .twall__run .twall__card[aria-hidden="true"]{display:none}
  .twall__run .twall__card{margin-bottom:var(--space-6)}
}
.twall__card{
  /* A card must not be split across a column break, and the bottom margin is
     the row gap — column-gap only spaces the columns themselves. The marquee
     lays its cards out with flex gap instead, so it clears the margin. */
  break-inside:avoid;
  margin:0 0 var(--space-6);
  display:flex;flex-direction:column;gap:var(--space-8);
  padding:var(--space-8);
  background:var(--bg-surface,#fff);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);
}
.twall__run .twall__card{margin-bottom:0}
.twall__top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4)}
.twall__by{display:flex;flex-direction:column;gap:2px;min-width:0}
.twall__name{font-size:var(--fs-body-lg);font-weight:800;color:var(--fg-primary);line-height:1.5}
.twall__role{font-size:var(--fs-body-sm);color:var(--fg-muted);line-height:1.7}
/* The quote glyph sits where the reference puts the client photo. Amber is a
   fill here, not text colour, so the accent rule still holds. */
.twall__mark{
  flex:0 0 auto;
  display:grid;place-items:center;
  width:44px;height:44px;border-radius:var(--radius-full);
  background:var(--accent-default);color:var(--fg-on-accent);
  font-size:30px;line-height:0;padding-top:14px;
  font-family:var(--font-fa);
}
.twall__quote{
  margin:0;font-size:var(--fs-body);line-height:1.95;color:var(--fg-secondary);
}

/* With only a couple of quotes the wall would read as a grid that failed to
   load. Fewer, larger cards read as a deliberate pull-quote pair instead: the
   type steps up, the card gets more air, and the row still fills the band. */
.twall--few{column-gap:var(--space-8);max-width:1120px;margin-inline:auto}
.twall--few .twall__card{margin-bottom:var(--space-8)}
.twall--few .twall__card{padding:var(--space-10);gap:var(--space-10)}
.twall--few .twall__quote{font-size:var(--fs-body-lg);line-height:1.9}
.twall--few .twall__mark{width:52px;height:52px;font-size:36px;padding-top:17px}
@media (max-width:640px){
  .twall--few .twall__card{padding:var(--space-6);gap:var(--space-6)}
  .twall--few .twall__quote{font-size:var(--fs-body)}
}


@media (max-width:900px){
  .twall{columns:min(var(--twall-cols,3),2);column-gap:var(--space-5)}
  .twall--marquee{grid-template-columns:repeat(min(var(--twall-cols,3),2),1fr);height:520px}
}
@media (max-width:640px){
  .twall{columns:1}
  /* Same treatment as reduced-motion: one readable stack, nothing cropped. */
  .twall--marquee{display:block;height:auto;overflow:visible;-webkit-mask-image:none;mask-image:none}
  .twall--marquee .twall__col{overflow:visible}
  .twall--marquee .twall__run{animation:none;display:block}
  .twall--marquee .twall__run .twall__card[aria-hidden="true"]{display:none}
  .twall--marquee .twall__run .twall__card{margin-bottom:var(--space-6)}
  .twall__card{padding:var(--space-6)}
}

.svc-gallery__item img{
  width:100%;height:auto;display:block;
  border-radius:var(--radius-lg);
  border:1px solid var(--border-hairline);
}
@media (max-width:900px){.svc-gallery{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.svc-gallery{grid-template-columns:1fr}}


/* Informative hero art — it carries its own captions, so the radial feather is
   dropped: it would erase the very text the image exists to show.

   No frame. The asset is a cut-out on transparency, so a border and radius
   would draw a rectangle around empty space rather than around the artwork.
   Only the height is constrained, and the box shrinks to match, so a portrait
   cut-out never pushes the hero taller than the copy beside it. */
/* Art whose white ground is baked into the file rather than transparent.
   These two are animated WebP built from GIFs, and GIF has no real alpha — the
   corner pixel is a solid 255,255,255. No CSS can remove a background that is
   part of the image, which is why removing the plate did nothing.

   multiply is what does remove it: 255 multiplied against the page ground
   returns the page ground exactly, so the white box disappears and only the
   shape survives. This is the same mechanism .hero__render already uses on the
   contact page, where the identical white-grounded render reads correctly.

   The feather comes off too: on a cut-out shape it dissolved the only edge the
   art had. */
.svc-art--surface{
  -webkit-mask-image:none;
          mask-image:none;
  mix-blend-mode:multiply;
}

.svc-art--info{
  -webkit-mask-image:none;
          mask-image:none;
  width:auto;
  max-width:min(46%,540px);
}
.svc-art--info img{
  max-height:min(78vh,580px);
  width:auto;height:auto;
}
@media (max-width:960px){.svc-art{display:none}}   /* the copy leads on small screens */

/* The services pages name themselves instead of repeating MANGO, so the mark
   carries Persian and has to be set in the Persian face — the Latin display
   font has no Persian glyphs and would silently fall back mid-word. Length
   varies from «سئو و پشتیبانی» to «طراحی سایت آتلیه عکاسی», so the size is
   derived from the character count the template measures rather than fixed. */
.hero__mark--svc{
  font-family:var(--font-fa);
  font-weight:900;
  direction:rtl;
  letter-spacing:-.01em;          /* Persian does not take the Latin -.03em */
  font-size:clamp(34px, calc(84vw / (var(--mark-len,8) * 0.62)), 132px);
}

.hero__render{
  position:absolute;z-index:2;
  /* Was left:0, which pins the art to the VIEWPORT edge. At 1440 that is only
     75px outside the content column and looked deliberate; on a 1920 screen the
     container starts at 315px and the render hung a quarter of the way into the
     margin, cut off by the hero's own overflow. Same container-relative offset
     the service art uses. */
  left:calc((100% - min(100%, 1440px)) / 2 + var(--wrap-pad));
  top:50%;transform:translateY(-50%);
  width:min(52%,660px);   /* was 64%/880: the sources are 800px wide, so the
                             old box upscaled them and softened every edge */
  pointer-events:none;
  mix-blend-mode:multiply;
  /* The supplied plate is #F7F7F9 and the reference's own wordmark is baked
     into it in PURE WHITE. Under multiply, 255 maps to the page ground but
     247 maps darker — so BOTH the plate box and that wordmark would show.
     Lifting the plate to 255 makes the plate and the baked wordmark vanish
     together, leaving only the shaded 3D objects. See BUILD-NOTES.md.       */
  filter:brightness(1.035) contrast(1.03) saturate(1.04);
  -webkit-mask-image:radial-gradient(ellipse 60% 64% at 52% 50%,#000 68%,transparent 100%);
          mask-image:radial-gradient(ellipse 60% 64% at 52% 50%,#000 68%,transparent 100%);
}
.hero__render video{width:100%;height:auto}

.hero__inner{position:relative;z-index:3;display:grid;grid-template-columns:1fr;justify-items:start}
.hero__copy{max-width:600px;text-align:right}

/* Measured: two groups, the second indented +153px against a 64px heading.
   The heading is now 40px, so the indent is scaled by the same ratio
   (153 x 40/64 = 96) — otherwise the step dwarfs the type.               */
.hero__h1{margin-bottom:var(--space-8);width:100%}
.hero__h1 .g{display:block}
.hero__h1 .g--b{margin-right:96px}

.hero__meta{display:flex;flex-wrap:wrap;gap:var(--space-10);margin-top:var(--space-10)}
.stat__n{
  font-family:var(--font-en-mono);
  font-size:34px;line-height:1;font-weight:400;
  color:var(--accent-text);              /* accent TEXT token — 5.38:1 */
  direction:ltr;display:inline-block;
}
.stat__l{margin-top:var(--space-3);font-size:var(--fs-body-sm);color:var(--fg-muted);max-width:22ch;line-height:1.7}

.hero__actions{display:flex;flex-wrap:wrap;gap:var(--space-4);margin-top:var(--space-10)}

@media (max-width:1100px){
  .hero{padding-block:var(--space-8) var(--space-12)}
  .hero__render{position:relative;inset:auto;left:auto;transform:none;
    width:min(96%,560px);margin:0 auto var(--space-2);
    -webkit-mask-image:radial-gradient(ellipse 68% 66% at 50% 50%,#000 62%,transparent 100%);
            mask-image:radial-gradient(ellipse 68% 66% at 50% 50%,#000 62%,transparent 100%)}
  /* left is inherited from the container-relative rule above; the old -6%
     pushed the M clean off the viewport (-65px at 1100, -22px at 390). */
  .hero__mark{font-size:clamp(88px,24vw,170px);top:20%}
  .hero__mark--pf{font-size:clamp(56px,15vw,110px)}
  .hero__inner{justify-items:stretch}
  .hero__copy{max-width:none}
  .hero__h1 .g--b{margin-right:28px}
}
@media (max-width:640px){
  .hero__h1 .g--b{margin-right:0}
  .hero__meta{gap:var(--space-8)}
}

/* --- service page: breadcrumb + shorter hero --------------------------- */
.svc-hero{padding-block:var(--space-12) var(--space-16)}
.crumb{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);
  font-size:var(--fs-body-sm);color:var(--fg-muted);margin-bottom:var(--space-5)}
.crumb a{transition:color .2s var(--ease)}
.crumb a:hover{color:var(--accent-text)}
.crumb span[aria-current]{color:var(--fg-primary);font-weight:600}

/* ============================ SECTION HEAD ============================== */
/* RTL GOTCHA: the document is RTL, so on the cross axis `flex-start` is the
   RIGHT edge and `flex-end` is the LEFT edge. A right-aligned section
   therefore wants flex-START, and the left-aligned one wants flex-END.     */
.sec-head{display:flex;flex-direction:column;margin-bottom:var(--space-10)}

/* A section head with an action opposite the title. In RTL the first child
   (the title block) lands on the right and the button on the left, which is
   what space-between gives us for free — no ordering tricks needed.      */
.sec-head--split{
  flex-direction:row;
  flex-wrap:wrap;
  align-items:flex-end;
  justify-content:space-between;
  gap:var(--space-6) var(--space-8);
}
.sec-head--split > :first-child{flex:1 1 22rem}
.sec-head--split .btn{flex:0 0 auto}
@media (max-width:700px){
  .sec-head--split{align-items:stretch}
  .sec-head--split .btn{align-self:flex-start}   /* RTL: hugs the right edge */
}
.section--rtl .sec-head{align-items:flex-start;text-align:right}
/* NOTE: `--ltr` is currently unused. CLAUDE.md calls for the work section to
   flip left so the page alternates, but the client asked for every title to
   be right-aligned. Swap a section's class back to re-enable the rhythm.  */
.section--ltr .sec-head{align-items:flex-end;text-align:left}

/* Clients section — client-set, its own hook so it can be tuned without
   touching every other section head. NOTE: .sec-head--split is a ROW flex, so
   align-items here is the VERTICAL axis: flex-end sits the title's baseline
   block with the render beside it, rather than moving the title left. */
.section--clients .sec-head{align-items:flex-end}
/* Was capped at 20ch, which stacked «خدمات آژانس خلاقیت منگو در طراحی سایت
   آتلیه عکاسی» over three lines. Client-set: these run on one line where the
   measure allows, and still wrap on their own on narrower screens. */
.sec-head .h-sec{max-width:none}
/* Full width, per the client. .lede carries a 52ch measure globally, which is
   right for the hero's short standfirst but wrong here: several service pages
   put a fifteen-line explainer in this slot, and capping it at 458px left the
   other 800px of the row empty and the text looking wedged into a corner.
   The hero and card ledes keep their measure — only the section head opts out. */
.sec-head .lede{margin-top:var(--space-6);max-width:none}
/* Was text-align:right, which out-specifies .lede and cancelled its justify.
   The paragraph is already RTL, so it needs no alignment of its own. */
.section--rtl .sec-head .lede{text-align:justify}

/* The marker lives in the OUTER margin, 67px in from the page edge. */
/* Hidden at the client's request. The numbers are printed by 20 spans across
   16 templates, and the markup (plus the `marker` args and the per-page
   counters that feed it) is left in place so this is a one-line revert. The
   positioning rules below stay for the same reason. */
.marker--edge{display:none}

.marker--edge{position:absolute;top:calc(var(--section-pad) - 44px)}
.section--rtl .marker--edge{right:67px}
.section--ltr .marker--edge{left:67px}
@media (max-width:1100px){.marker--edge{position:static;margin-bottom:var(--space-4)}}

/* ============================ SERVICES ================================== */
/* A plain 4-up row with 75px margins. It does NOT bleed off the canvas.
   All 8 services are shown, so it wraps to a second row of 4.            */
.svc-row{display:grid;grid-template-columns:repeat(4,1fr);gap:30px}
.svc-row > div{display:flex}            /* let each wrapper stretch its card */
.svc-row > div > .card{flex:1 1 auto}
@media (max-width:1100px){.svc-row{grid-template-columns:repeat(2,1fr);gap:20px}}
@media (max-width:640px) {.svc-row{grid-template-columns:1fr}}

/* ============================ PROCESS =================================== */
/* Separators are drawn as item borders, not as a background showing through a
   1px gap. With the gap trick any leftover cell in the last row — five items in
   a three-column grid, which is exactly what a service page has — renders as a
   grey block, because there is no item to cover the container's background. */
.steps{display:grid;grid-template-columns:repeat(3,1fr);
  border:1px solid var(--border-hairline);border-radius:var(--radius-lg);overflow:hidden}
.steps .step{border-inline-start:1px solid var(--border-hairline);
  border-top:1px solid var(--border-hairline)}
.steps .step:nth-child(3n+1){border-inline-start:0}
.steps .step:nth-child(-n+3){border-top:0}
@media (max-width:900px){
  .steps{grid-template-columns:repeat(2,1fr)}
  .steps .step{border-inline-start:1px solid var(--border-hairline);border-top:1px solid var(--border-hairline)}
  .steps .step:nth-child(n){border-inline-start:1px solid var(--border-hairline)}
  .steps .step:nth-child(2n+1){border-inline-start:0}
  .steps .step:nth-child(-n+2){border-top:0}
}
@media (max-width:600px){
  .steps{grid-template-columns:1fr}
  .steps .step{border-inline-start:0;border-top:1px solid var(--border-hairline)}
  .steps .step:first-child{border-top:0}
}
.step{background:var(--bg-page);padding:var(--space-10) var(--space-8);transition:background-color .3s var(--ease)}
.step:hover{background:var(--bg-surface)}
.step__n{font-family:var(--font-en-mono);font-size:12px;letter-spacing:.14em;color:var(--accent-text);direction:ltr}
.step__t{margin-top:var(--space-5);font-size:var(--fs-body-lg);font-weight:800;line-height:1.5}
.step__d{margin-top:var(--space-3);font-size:var(--fs-body-sm);color:var(--fg-muted);line-height:1.9}

/* ============================ WORK (flips to LEFT) ====================== */
/* ============================ NEWS / ARTICLES ==========================
   A 3-up row of article cards. Lighter than the service cards — these are
   editorial, so the date leads and the title carries the weight.
   ====================================================================== */
/* Four across, and it STAYS four across — there is deliberately no 3-column
   step, because a 1200px breakpoint silently turned this into a 3-up row on
   ordinary laptop widths. minmax(0,1fr) stops long titles forcing a column
   wider than its share. */
.news-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-5)}
@media (max-width:860px){.news-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.news-grid{grid-template-columns:1fr}}

.post{
  display:flex;flex-direction:column;
  border-radius:var(--radius-lg);
  background:var(--bg-surface);
  box-shadow:var(--shadow-card);
  overflow:hidden;                 /* clips the cover to the card radius */
  text-align:right;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease);
}
.post:hover{transform:translateY(-6px);box-shadow:var(--shadow-lift)}
.post:hover .affordance{background:var(--accent-default);color:var(--fg-on-accent);transform:rotate(-45deg)}

/* --- cover ------------------------------------------------------------ */
.post__cover{
  position:relative;
  aspect-ratio:3/2;                /* every card keeps the same shape */
  background:var(--bg-subtle);
  overflow:hidden;
}
.post__cover img{
  width:100%;height:100%;
  object-fit:cover;
  display:block;
  transition:transform .6s var(--ease);
}
.post:hover .post__cover img{transform:scale(1.05)}

/* the affordance floats on the cover, on the RTL-outward (left) corner */
.post__cover .affordance{
  position:absolute;top:var(--space-4);left:var(--space-4);
  background:rgba(255,255,255,.92);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  box-shadow:none;
}

/* --- body ------------------------------------------------------------- */
.post__body{display:flex;flex-direction:column;flex:1 1 auto;padding:var(--space-5) var(--space-5) var(--space-6)}
.post__date{font-size:13px;color:var(--fg-muted);letter-spacing:.01em;margin-bottom:var(--space-3)}
.post__title{
  font-size:var(--fs-body);font-weight:800;line-height:1.8;letter-spacing:-.01em;
  /* Clamp AND reserve two lines, so a one-line title still occupies the same
     block and stacked cards stay the same height as side-by-side ones.     */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  min-height:3.6em;
}
.post__desc{margin-top:var(--space-3);font-size:var(--fs-body-sm);line-height:1.9;color:var(--fg-muted);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.post__more{margin-top:auto;padding-top:var(--space-5);
  display:flex;align-items:center;gap:var(--space-2);
  font-size:var(--fs-body-sm);font-weight:700;color:var(--accent-text)}
.post__more .arw{transition:transform .3s var(--ease)}
.post:hover .post__more .arw{transform:translateX(-4px)}

.news-more{display:flex;justify-content:flex-start;margin-top:var(--space-10)}

/* ============================ SEO PANEL (dark) ========================== */
.seo{position:relative;background:var(--bg-panel-dark);border-radius:var(--radius-xl);
  padding:var(--space-16) 0;overflow:hidden;margin-block:var(--space-8)}
.seo::before{content:"";position:absolute;inset:0;
  background-image:url("../img/texture-dark.png");background-size:cover;opacity:.5}
.seo__inner{position:relative;z-index:2}
/* The panel no longer holds a render, so the copy runs full width and the
   three services sit in a row underneath it. */
.seo__list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--space-8);margin-top:var(--space-10)}
@media (max-width:860px){.seo__list{grid-template-columns:1fr;gap:var(--space-6)}}
.seo__item{display:flex;gap:var(--space-5);align-items:flex-start;
  padding-inline-end:var(--space-6);border-inline-end:1px solid var(--border-on-dark)}
.seo__item:last-child{border-inline-end:0;padding-inline-end:0}
@media (max-width:860px){
  .seo__item{padding-inline-end:0;border-inline-end:0;
    padding-bottom:var(--space-5);border-bottom:1px solid var(--border-on-dark)}
  .seo__item:last-child{border-bottom:0;padding-bottom:0}
}
.seo__k{flex:0 0 auto;width:38px;height:38px;border-radius:var(--radius-full);
  background:var(--accent-default);color:var(--fg-on-accent);
  display:grid;place-items:center;font-family:var(--font-en-mono);font-size:12px;direction:ltr}
.seo__t{font-size:var(--fs-body-lg);font-weight:800;line-height:1.5}
.seo__d{margin-top:var(--space-2);font-size:var(--fs-body-sm);color:var(--fg-on-dark-muted);line-height:1.9}

/* ============================ FAQ ======================================= */
/* Each question is its own panel rather than a row in a ruled list: numbered
   marker at the leading edge, question, then a dark round + at the trailing
   edge. Both FAQ generators (the front page part and single-service.php) emit
   the same classes, so this restyles both without touching their markup — the
   number comes from a counter for the same reason. */
.faq{display:flex;flex-direction:column;gap:var(--space-3);counter-reset:faq}
.faq__item{
  counter-increment:faq;
  background:var(--bg-surface,#fff);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-xl);
  overflow:hidden;
}
.faq__q{
  width:100%;display:flex;align-items:center;gap:var(--space-5);
  padding:var(--space-6) var(--space-8);background:transparent;border:0;cursor:pointer;
  font-size:var(--fs-body-lg);font-weight:700;line-height:1.6;
  text-align:right;color:var(--fg-primary);
  transition:color .22s var(--ease)
}
.faq__q:hover{color:var(--accent-text)}
/* `persian` is a predefined CSS counter style, so the numerals match the rest
   of the site without a helper in the markup. */
.faq__q::before{
  content:counter(faq, persian);
  flex:0 0 auto;
  display:grid;place-items:center;
  min-width:30px;height:30px;padding:0 var(--space-2);
  border-radius:var(--radius-full);
  background:var(--bg-page);border:1px solid var(--border-hairline);
  font-size:var(--fs-body-sm);font-weight:700;color:var(--fg-muted);
}
/* auto margin rather than space-between: with the counter there are three flex
   items, and space-between would strand the question in the middle instead of
   keeping it next to its number. */
.faq__ico{flex:0 0 auto;margin-inline-start:auto;
  width:44px;height:44px;border-radius:var(--radius-full);
  display:grid;place-items:center;font-size:22px;line-height:1;font-weight:400;
  background:var(--bg-panel-dark,#2D4059);color:#fff;
  transition:transform .32s var(--ease),background-color .22s var(--ease),color .22s var(--ease)}
.faq__q[aria-expanded="true"] .faq__ico{transform:rotate(45deg);background:var(--accent-default);color:var(--fg-on-accent)}
/* grid-template-rows, not max-height. The old version animated a layout
   property and needed JS to measure scrollHeight and re-measure on every
   resize; 0fr->1fr animates the same reveal off the real content height with
   no measurement and no magic cap. */
.faq__a{
  display:grid;
  /* minmax(0,…), not a bare 0fr: a bare 0fr still takes the item's min-content
     contribution as its base size, and the answer's own bottom padding kept the
     closed panel 32px tall. The explicit 0 minimum collapses it properly. */
  grid-template-rows:minmax(0,0fr);
  overflow:hidden;
  transition:grid-template-rows .38s var(--ease);
}
.faq__q[aria-expanded="true"] + .faq__a{grid-template-rows:minmax(0,1fr)}
.faq__a>div{min-height:0;overflow:hidden}
@media (prefers-reduced-motion:reduce){.faq__a{transition-duration:.01ms}}
.faq__a>div{padding:0 var(--space-8) var(--space-8);color:var(--fg-muted);line-height:1.95;max-width:76ch}
.faq__a ul{margin-top:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)}
.faq__a li{position:relative;padding-inline-start:var(--space-5)}
.faq__a li::before{content:"";position:absolute;inset-inline-start:0;top:.85em;
  width:6px;height:6px;border-radius:50%;background:var(--accent-default)}

/* The 3D render that used to sit inside the dark SEO panel now sits here, on
   the light ground — so it keys the same way the hero does: lift the near
   white plate to 255, then multiply it away against bg/page. No card. */
/* The supplied render only fills ~35% of its frame width (object centred at
   53.7% / 56.4%, not 50/50), so widening the box just adds empty plate — it
   has to be cropped into instead.

   The blend lives on the VIDEO and composites against a ground the crop box
   paints itself. Putting mix-blend-mode on the outer element instead makes it
   blend against the nearest stacking-context ancestor — here `.sec-head.rv`,
   which is transparent because of its reveal transform — and the white plate
   comes back as a hard-edged rectangle. That also broke permanently under
   prefers-reduced-motion, where the reveal never clears will-change. */
.clients__render{
  /* sits opposite the heading: the section head is a split row, so in RTL the
     heading takes the right and this lands on the left with no ordering trick */
  flex:0 0 auto;
  width:min(34%,400px);
  /* The ellipse radius must be ~50% of the box: a radial-gradient's 100% stop
     sits at the ellipse edge, so a 72% ellipse was still near-opaque where the
     box actually ends and the object's shadow got a hard clipped edge. */
  -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 58%,transparent 100%);
          mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 58%,transparent 100%);
}
.clients__crop{
  position:relative;
  aspect-ratio:4/3;
  overflow:hidden;
  background:var(--page-bg);      /* fixed attachment, so it lines up with the page */
  isolation:isolate;
}
/* Image renders reuse the crop's ratio, ground and isolation, but not the
   video tuning below: that scale and transform-origin were measured against
   the two mp4s and would crop these off-centre. These subjects are centred in
   their own frame, so they scale from the middle instead.

   Shown at 1:1 the subject occupies barely half the frame and only its soft
   shadow reads at hero size, so the box is scaled into the way the videos are.
   The radial mask feathers the crop edge; without it the plate ends on a hard
   line across the wordmark behind it. */
.clients__crop--img{
  -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 55%,transparent 100%);
          mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 55%,transparent 100%);
  background:transparent;   /* the mask already feathers; an opaque plate would show its own edge */
}
.clients__crop--img img{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
  transform:scale(1.06);
  mix-blend-mode:multiply;
}

.clients__crop video{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
  transform:scale(1.5);
  transform-origin:53.7% 56.4%;   /* the object's own centre, measured */
  mix-blend-mode:multiply;
  filter:brightness(1.035) contrast(1.03);
  display:block;
}
@media (max-width:860px){.clients__render{width:min(72%,320px);align-self:center}}

/* ============================ CLIENTS ==================================
   Two marquee rows drifting in opposite directions. Pure CSS: the track
   holds the chips twice and slides exactly one copy's width, so the second
   copy lands where the first began — seamless, no JS, no jump.

   RTL GOTCHA: in an RTL flex row the track is laid out right-to-left, so the
   duplicate copies sit to the LEFT of the original and the track's right edge
   is pinned to the container's. Translating NEGATIVE therefore drags
   everything off-screen and the row empties out; the copies only slide into
   view on a POSITIVE translate. Hence 50%, not -50%.

   The markup repeats each row FOUR times, so the 50% shift equals two whole
   copies. Two conditions have to hold or the loop shows a seam: the shift
   must be a whole number of copies, and it must be at least as wide as the
   container — with only two copies the shorter rows left a visible gap at
   the trailing edge.
   ====================================================================== */
.marquee{
  position:relative;
  overflow:hidden;
  padding-block:var(--space-2);
  /* fade the two ends so chips enter and leave instead of being chopped */
  -webkit-mask-image:linear-gradient(to right,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(to right,transparent,#000 8%,#000 92%,transparent);
}
.marquee + .marquee{margin-top:var(--space-4)}

.marquee__track{
  display:flex;
  width:max-content;
  gap:var(--space-3);
  animation:marquee 46s linear infinite;
  will-change:transform;
}
/* the second row drifts the other way */
.marquee--rev .marquee__track{animation-direction:reverse}
.marquee--slow .marquee__track{animation-duration:58s}
.marquee--slower .marquee__track{animation-duration:70s}

/* Pointing at a row holds that row, so a name can be read. */
.marquee:hover .marquee__track,
.marquee__track:focus-within{animation-play-state:paused}

@keyframes marquee{ from{transform:translateX(0)} to{transform:translateX(50%)} }

.chip{
  flex:0 0 auto;
  padding:var(--space-3) var(--space-6);border-radius:var(--radius-full);
  background:var(--bg-surface);box-shadow:inset 0 0 0 1px var(--border-hairline);
  font-size:var(--fs-body-sm);font-weight:600;color:var(--fg-secondary);
  white-space:nowrap;
  transition:background-color .25s var(--ease),color .25s var(--ease),transform .25s var(--ease);
}
.chip:hover{background:var(--accent-default);color:var(--fg-on-accent);transform:translateY(-2px)}

@media (prefers-reduced-motion:reduce){
  /* no drift — let the row be swiped/scrolled by hand instead */
  .marquee{overflow-x:auto;-webkit-mask-image:none;mask-image:none;
    scrollbar-width:none;scroll-snap-type:x proximity}
  .marquee::-webkit-scrollbar{display:none}
  .marquee__track{animation:none}
}

/* ============================ CONTACT ===================================
   Rebuilt to match the reference's contact section (#Cont-Sec):
   a LIGHT ground (the reference section is #F7F7F9 — our bg/page), a white
   card, and a dark texture tile inside the card carrying the heading.
   RTL mirror: the tile holds the copy, so it sits on the reading start —
   the RIGHT — and the form sits to its left.
   Measured from the reference: heading 46/46, fields 50 tall with a 1px
   underline only (no fill, no radius), textarea 270 tall.
   ====================================================================== */
/* `Frame.svg` is the supplied 1920x920 white 3D perspective floor — the same
   ground the reference sits its contact card on. It anchors to the bottom so
   the floor recedes under the card, exactly as in the reference.          */
.contact{
  background:
    url("../img/Frame.svg") center bottom / cover no-repeat,
    var(--page-bg);
}

.contact-card{
  display:grid;
  grid-template-columns:345px 1fr;      /* measured: tile 345 of a 1150 card */
  background:var(--bg-surface);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lift);
  overflow:hidden;
}

/* --- the dark tile ---------------------------------------------------- */
.contact-tile{
  position:relative;
  display:flex;flex-direction:column;justify-content:flex-start;
  padding:var(--space-12) var(--space-10);
  color:var(--fg-on-dark);
  background:var(--ink-950);
  isolation:isolate;
  min-height:420px;   /* presence, without forcing the card taller than the form */
}
/* The dark tile keeps the `Frame 2775` plate, as in the reference. */
.contact-tile::before{
  content:"";position:absolute;inset:0;z-index:-1;
  background:url("../img/texture-dark.png") center / cover no-repeat;
}
.contact-tile::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to bottom,rgba(10,15,22,.90) 0%,rgba(10,15,22,.58) 46%,rgba(10,15,22,.30) 100%);
}
.contact-tile .h-sec{max-width:10ch}           /* forces the short stacked lines */
.contact-tile .h-sec .hl{white-space:nowrap}   /* keep the amber phrase intact */
.contact-tile p{margin-top:var(--space-6);font-size:var(--fs-body-sm);
  line-height:1.9;color:var(--fg-on-dark-muted);max-width:30ch}

/* --- the form side ---------------------------------------------------- */
.contact-form{padding:var(--space-10)}
.contact-form__head{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:var(--space-4);margin-bottom:var(--space-8)}
.contact-form__head h3{
  font-size:var(--fs-h3);font-weight:800;letter-spacing:-.01em;line-height:1.3}
.contact-form__head .meta{display:flex;flex-wrap:wrap;gap:var(--space-5);
  font-size:var(--fs-body-sm);color:var(--fg-muted)}
.contact-form__head .meta a{transition:color .2s var(--ease)}
.contact-form__head .meta a:hover{color:var(--accent-text)}

/* Single column. The two-column split put the message box beside the three
   short fields and left a tall ragged gap; one column reads as one task. */
.contact-grid{display:grid;grid-template-columns:1fr;gap:0}
.f-row{display:grid;grid-template-columns:1fr 1fr;gap:0 var(--space-6)}
@media (max-width:640px){.f-row{grid-template-columns:1fr}}

/* underline-only fields — no fill, no radius, exactly like the reference */
.f{display:flex;flex-direction:column;margin-bottom:var(--space-5)}
.f > label{font-size:var(--fs-body-sm);color:var(--fg-secondary);margin-bottom:var(--space-1)}
.f .req{color:var(--accent-text)}
.f input,.f textarea{
  width:100%;background:transparent;color:var(--fg-primary);
  border:0;border-bottom:1px solid var(--border-hairline);
  border-radius:0;padding:var(--space-2) 0;
  font-size:var(--fs-body);line-height:1.8;
  transition:border-color .22s var(--ease);
}
.f input{height:34px}
.f textarea{min-height:130px;resize:vertical}
/* placeholders carry format examples (phone shape), so they must be legible */
.f input::placeholder,.f textarea::placeholder{color:var(--fg-muted);opacity:1}
.f input:focus,.f textarea:focus{outline:none;border-bottom-color:var(--accent-default);border-bottom-width:2px}
.f--invalid input,.f--invalid textarea{border-bottom-color:var(--support-danger)}

.counter{align-self:flex-start;margin-top:var(--space-2);
  font-family:var(--font-en-mono);font-size:11px;color:var(--fg-muted);direction:ltr}

.consent{margin-top:var(--space-5);font-size:13px;line-height:1.85;color:var(--fg-muted);max-width:70ch}

/* --- bottom row: submit ---------------------------------------------- */
.contact-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-5);
  margin-top:var(--space-8)}

/* status line after a redirect, plus the honeypot nobody should ever see */
.form__note{margin-top:var(--space-5);font-size:13px;line-height:1.8;color:var(--fg-muted)}
.form__note--ok,.form__note--error{
  margin-top:0;margin-bottom:var(--space-6);
  padding:var(--space-4) var(--space-5);border-radius:var(--radius-md);
  font-weight:600;
}
.form__note--ok{background:var(--bg-subtle);color:var(--fg-primary);
  box-shadow:inset 0 0 0 1px var(--border-hairline)}
.form__note--error{background:var(--bg-subtle);color:var(--support-danger);
  box-shadow:inset 0 0 0 1px var(--support-danger)}
/* Not display:none — some bots skip hidden inputs, and a field that is merely
   moved off-canvas still gets filled. */
.hp{position:absolute!important;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}

@media (max-width:1000px){
  .contact-card{grid-template-columns:1fr}
  .contact-tile{min-height:0;padding:var(--space-10) var(--space-8)}
  .contact-tile .h-sec{max-width:14ch}
  .contact-form{padding:var(--space-10) var(--space-8)}
  .f textarea{min-height:150px}
}
@media (max-width:600px){
  .contact-form,.contact-tile{padding-inline:var(--space-6)}
}

/* ============================ FOOTER ==================================== */
.site-footer{padding-block:var(--space-16) var(--space-10);border-top:1px solid var(--border-hairline);margin-top:var(--space-16)}
.foot{display:grid;grid-template-columns:1.6fr 1fr 1fr 1.3fr;gap:var(--space-12)}
@media (max-width:1000px){.foot{grid-template-columns:1fr 1fr;gap:var(--space-10)}}
@media (max-width:600px) {.foot{grid-template-columns:1fr}}
.foot__brand img{height:48px;width:auto;margin-bottom:var(--space-5)}
.foot h3{font-size:var(--fs-body);font-weight:800;margin-bottom:var(--space-5)}
.foot ul{display:flex;flex-direction:column;gap:var(--space-1)}
/* The footer links were 22px tall — a whole column of them, and on a phone the
   footer is where the nav actually gets used. The text size is unchanged; the
   padding is what grows, so the tap area clears 44px without the column
   looking any different. */
.foot a{
  display:inline-block;
  padding-block:var(--space-3);
  font-size:var(--fs-body-sm);color:var(--fg-muted);
  transition:color .2s var(--ease);
}
.foot a:hover{color:var(--accent-text)}
.foot__contact li{font-size:var(--fs-body-sm);color:var(--fg-muted);line-height:1.9}
.foot__contact b{color:var(--fg-secondary);font-weight:600}

/* Client-set: the desktop columns read too airy. The generous padding above
   exists for the 44px touch target, which only matters on a phone — so it is
   left alone below 1000px and relaxed above, where the pointer is a mouse. */
@media (min-width:1001px){
  .foot a{padding-block:var(--space-1)}
  .foot h3{margin-bottom:var(--space-4)}
  .foot__contact li{line-height:1.7}
  .foot__contact{gap:var(--space-2)}
}
.foot__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-4);
  margin-top:var(--space-16);padding-top:var(--space-8);border-top:1px solid var(--border-hairline);
  font-size:13px;color:var(--fg-muted)}
.social{display:flex;gap:var(--space-2)}
.social a{width:44px;height:44px;border-radius:var(--radius-full);display:grid;place-items:center;
  box-shadow:inset 0 0 0 1px var(--border-hairline);transition:background-color .22s var(--ease),color .22s var(--ease)}
.social a:hover{background:var(--accent-default);color:var(--fg-on-accent)}

/* ============================ REVEAL ==================================== */
/* Scroll reveal. Longer and softer than a default fade so it actually reads
   as motion; the easing is a slow-settling ease-out rather than a linear-ish
   curve. Children of a grid cascade via a per-group delay set in site.js.  */
.rv{
  opacity:0;
  transform:translateY(34px);
  transition:opacity .95s cubic-bezier(.16,.84,.44,1),
             transform .95s cubic-bezier(.16,.84,.44,1);
  will-change:opacity,transform;
}
.rv.in{opacity:1;transform:none;will-change:auto}
@media (prefers-reduced-motion:reduce){
  /* will-change must be cleared too: left set, it keeps a stacking context
     alive and any mix-blend-mode inside it stops reaching the page ground. */
  .rv{opacity:1;transform:none;transition:none;will-change:auto}
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}


/* ======================= PROJECT PAGE (single portfolio) ===============
   The old .spec grid and .gal grid were replaced by .specpanel and the .pgal
   carousel (see the project hero / spec panel block above).
   ====================================================================== */

.gal{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-6)}
@media (max-width:760px){.gal{grid-template-columns:1fr}}
.gal__item{
  margin:0;border-radius:var(--radius-lg);overflow:hidden;
  background:var(--bg-subtle);box-shadow:var(--shadow-card);
}
.gal__item img{width:100%;height:auto;display:block}

/* Long-form project copy — the only place raw editor output renders. */
.prose{max-width:78ch;color:var(--fg-secondary);line-height:1.95}
.prose p{margin:0 0 var(--space-5)}
.prose p:last-child{margin-bottom:0}


/* ============================ ABOUT / CONTACT =========================
   Two page-level blocks: the "at a glance" counters and the direct-contact
   tiles. Both reuse the hairline-bordered grid the process steps use, so the
   pages read as part of the same system rather than new inventions.
   ====================================================================== */
.stats{
  display:grid;grid-template-columns:repeat(4,1fr);
  border:1px solid var(--border-hairline);border-radius:var(--radius-lg);overflow:hidden;
}
@media (max-width:860px){.stats{grid-template-columns:repeat(2,1fr)}}
@media (max-width:480px){.stats{grid-template-columns:1fr}}
.stat{
  background:var(--bg-page);padding:var(--space-10) var(--space-8);
  border-inline-start:1px solid var(--border-hairline);
  transition:background-color .3s var(--ease);
}
.stat:first-child{border-inline-start:0}
@media (max-width:860px){
  .stat:nth-child(2n+1){border-inline-start:0}
  .stat:nth-child(n+3){border-top:1px solid var(--border-hairline)}
}
@media (max-width:480px){
  .stat{border-inline-start:0}
  .stat+.stat{border-top:1px solid var(--border-hairline)}
}
.stat:hover{background:var(--bg-surface)}
/* Client-set: amber and Latin, matching the counters in the home hero. The
   face is Michroma, which carries no Persian numerals, so Latin digits are also
   the only ones that actually render in it. */
.stat__big{
  display:block;
  font-family:var(--font-en-mono);
  font-size:var(--fs-section);line-height:1;font-weight:400;
  color:var(--accent-text);          /* accent TEXT token — 5.38:1, not the fill */
  direction:ltr;
}
.stat__cap{margin-top:var(--space-4);font-size:var(--fs-body-sm);color:var(--fg-muted)}

.channels{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-6)}
@media (max-width:860px){.channels{grid-template-columns:1fr}}
.channel{
  position:relative;display:block;
  padding:var(--space-8);
  border-radius:var(--radius-lg);
  background:var(--bg-surface);box-shadow:var(--shadow-card);
  text-align:right;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease);
}
a.channel:hover{transform:translateY(-6px);box-shadow:var(--shadow-lift)}
a.channel:hover .affordance{background:var(--accent-default);color:var(--fg-on-accent);transform:rotate(-45deg)}
/* the non-link tile (the address) must not advertise an interaction it has none of */
div.channel .affordance{opacity:.35}
.channel .affordance{position:absolute;top:var(--space-6);left:var(--space-6)}
.channel__label{
  display:block;font-size:var(--fs-eyebrow);letter-spacing:.08em;
  color:var(--accent-text);font-weight:700;
}
.channel__value{
  margin-top:var(--space-5);
  font-size:var(--fs-body-lg);font-weight:800;line-height:1.7;color:var(--fg-primary);
  overflow-wrap:anywhere;                 /* the e-mail must not widen the column */
}
.channel__note{margin-top:var(--space-3);font-size:var(--fs-body-sm);color:var(--fg-muted)}


/* --- centred hero (single post) ----------------------------------------
   The article hero carries only a breadcrumb, the title and the date, with
   no wordmark or render behind it. Left-aligned in a 600px column it looked
   orphaned against all that empty space, so the whole block is centred.
   The `.g--b` indent is a two-group device the article title never uses. */
.hero--centered .hero__inner{justify-items:center}
.hero--centered .hero__copy{max-width:820px;text-align:center}
.hero--centered .crumb{justify-content:center}
.hero--centered .hero__h1{text-align:center}
.hero--centered .hero__h1 .g--b{margin-right:0}
.hero--centered .hero__actions{justify-content:center}


/* ============================ ARTICLE BODY =============================
   Long-form Persian. The measure is centred on the page and capped: at the
   full 1290px wrap a 16px paragraph runs past 150 characters a line, which
   is unreadable. Line-heights stay in the 185-195% band the Persian
   descenders need — do not tighten them to Latin values.
   ====================================================================== */
.article-shell{
  max-width:920px;
  margin-inline:auto;
  padding-block:0 var(--space-20);
}

/* Wider than the text measure — the cover is the article's opening image, not
   a paragraph. It still sits inside `.wrap` (1290px of content), so it never
   reaches the page edge. */
.article-cover{
  max-width:1120px;
  margin:0 auto var(--space-12);
  border-radius:var(--radius-lg);
  overflow:hidden;
  background:var(--bg-subtle);
  box-shadow:var(--shadow-card);
}
.article-cover img{width:100%;height:auto;display:block}

.entry{color:var(--fg-secondary);font-size:var(--fs-body);line-height:1.95}
.entry > *:first-child{margin-top:0}
.entry > *:last-child{margin-bottom:0}

.entry p{margin:0 0 var(--space-6)}
/* Images the live page keeps inside a prose section, shown where it keeps them
   rather than gathered into a gallery of their own. */
.entry__gallery{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:var(--space-6);
  margin:var(--space-8) 0;
}
/* ============================ ARTICLE TOC ============================== */
.toc{
  margin:0 0 var(--space-10);
  /* --space-9 was never defined (the scale runs 1-6, 8, 10, 12, 16, 20, 24, 32),
     so the whole shorthand was invalid at computed-value time and the box fell
     back to padding:0. space-10 keeps the author's "horizontal wider than
     vertical" intent using a token that exists. */
  padding:var(--space-8) var(--space-10);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  background:var(--bg-surface,#fff);
}
.toc__title{
  margin:0 0 var(--space-5);
  font-size:var(--fs-body-lg);font-weight:800;
  color:var(--fg-primary);
}
.toc__list{margin:0;padding:0;list-style:none;counter-reset:toc}
/* The gap between rows moved into the link itself: the anchors were 22px tall
   and there are up to 27 of them, so on a phone the outline was a column of
   targets too small to hit. Same visual rhythm, a real tap area. */
.toc__item{margin:0}
.toc__item a{
  display:block;
  padding-block:var(--space-2);
  color:var(--fg-secondary);
  text-decoration:none;
  line-height:1.8;
  transition:color .2s var(--ease);
}
.toc__item a:hover,.toc__item a:focus-visible{color:var(--accent-text);text-decoration:underline;text-underline-offset:4px}
/* The number is the h2 counter; h3s are indented under it without one, so the
   outline reads as sections and their parts rather than one flat list. */
.toc__item--l2{counter-increment:toc}
.toc__item--l2 > a::before{
  content:counter(toc) ". ";
  color:var(--accent-text);
  font-weight:700;
}
.toc__item--l3{padding-inline-start:var(--space-6);font-size:var(--fs-body-sm)}
/* Anchors land clear of the sticky header. */
.entry h2[id],.entry h3[id]{scroll-margin-top:calc(var(--header-h) + var(--space-4))}

.entry__gallery img{
  width:100%;height:auto;display:block;
  border-radius:var(--radius-lg);
  border:1px solid var(--border-hairline);
}

/* A captioned gallery pairs each shot with its own label. The imported content
   had the four images in one row and their four names as loose paragraphs
   underneath, so nothing said which name belonged to which screenshot; the
   figures carry that pairing in the markup now. */
.entry__gallery--captioned{align-items:start}
.entry__figure{margin:0;display:flex;flex-direction:column;gap:var(--space-3)}
.entry__caption{
  font-size:var(--fs-body-sm);
  line-height:1.7;
  color:var(--fg-secondary);
  text-align:center;
}
/* Running prose is justified with the lede. List items are left ragged: they
   are short enough that justifying them opens visible gaps between words. */
.entry p{text-align:justify;text-justify:inter-word;hyphens:none}

.entry h2,
.entry h3,
.entry h4{color:var(--fg-primary);line-height:1.5}
.entry h2{font-size:var(--fs-h2);font-weight:800;margin:var(--space-12) 0 var(--space-5)}
.entry h3{font-size:var(--fs-h3);font-weight:700;margin:var(--space-10) 0 var(--space-4)}
.entry h4{font-size:var(--fs-body-lg);font-weight:700;margin:var(--space-8) 0 var(--space-4)}
/* a heading is a section opener, so it never needs its top gap at the very top */
.entry h2:first-child,.entry h3:first-child{margin-top:0}

.entry ul,.entry ol{margin:0 0 var(--space-6);padding-inline-start:var(--space-8)}
.entry ul{list-style:disc}
.entry ol{list-style:decimal}
.entry li{margin-bottom:var(--space-3);line-height:1.9}
.entry li::marker{color:var(--accent-text)}
.entry li > ul,.entry li > ol{margin-top:var(--space-3)}

.entry strong,.entry b{font-weight:700;color:var(--fg-primary)}

.entry a{color:var(--accent-text);text-decoration:underline;text-underline-offset:4px;
  text-decoration-thickness:1px;transition:color .2s var(--ease)}
.entry a:hover{color:var(--fg-primary)}

.entry blockquote{
  margin:0 0 var(--space-6);padding:var(--space-6) var(--space-8);
  /* The bar was 3px of accent. This system's own rule is that borders are
     hairlines and amber is a fill, not a rule — so the quote is marked by the
     tinted ground and its own quote glyph, and the border returns to 1px. */
  border-inline-start:1px solid var(--border-hairline);
  background:var(--bg-subtle);border-radius:var(--radius-md);
  position:relative;
  color:var(--fg-primary);
}

.entry blockquote::before{
  content:"”";
  display:block;
  font-family:var(--font-en-mono);
  font-size:34px;line-height:1;
  color:var(--accent-text);
  margin-bottom:var(--space-2);
}

.entry figure{margin:var(--space-8) 0}
.entry img{max-width:100%;height:auto;display:block;border-radius:var(--radius-md)}
.entry figcaption{margin-top:var(--space-3);font-size:var(--fs-body-sm);color:var(--fg-muted);text-align:center}

/* --- editor-inserted media ------------------------------------------------
   Service and article bodies are written in the block editor, so an image the
   client drops between two paragraphs lands inside .entry. cleanup.php dequeues
   wp-block-library on the front end (the theme renders no blocks of its own),
   which also removes the alignment rules those blocks rely on — so the few that
   matter are declared here. Physical left/right on purpose: the editor's own
   preview uses the physical classes, and a mirrored result would surprise. */
.entry .wp-block-image{margin:var(--space-8) 0}
.entry .wp-block-image img{border-radius:var(--radius-md)}
.entry .aligncenter{margin-inline:auto}
.entry .alignleft{float:left;margin:0 var(--space-6) var(--space-5) 0;max-width:50%}
.entry .alignright{float:right;margin:0 0 var(--space-5) var(--space-6);max-width:50%}
.entry .alignwide{width:min(100%,1100px);margin-inline:auto}
.entry .alignfull{width:100%}
.entry figure.alignleft,.entry figure.alignright{margin-block:0 var(--space-5)}
/* A heading must never wrap around a floated image. */
.entry h2,.entry h3,.entry h4{clear:both}
.entry .wp-block-columns{display:flex;flex-wrap:wrap;gap:var(--space-8);margin:0 0 var(--space-6)}
.entry .wp-block-column{flex:1 1 240px;min-width:0}
.entry .wp-block-separator{border:0;border-top:1px solid var(--border-hairline);margin:var(--space-10) 0}
.entry .wp-block-buttons{display:flex;flex-wrap:wrap;gap:var(--space-4);margin:0 0 var(--space-6)}
.entry .wp-block-embed,.entry .wp-block-video{margin:var(--space-8) 0}
.entry .wp-block-embed iframe,.entry video{max-width:100%;border-radius:var(--radius-md)}
@media (max-width:700px){
  .entry .alignleft,.entry .alignright{float:none;margin-inline:0;max-width:100%}
}

/* Tables carry real price ranges in these articles, so they must stay legible;
   the wrapper scrolls on narrow screens rather than forcing the page wide. */
.entry table{
  width:100%;border-collapse:collapse;margin:0 0 var(--space-8);
  font-size:var(--fs-body-sm);
  border:1px solid var(--border-hairline);border-radius:var(--radius-md);overflow:hidden;
}
.entry thead{background:var(--bg-subtle)}
.entry th,.entry td{padding:var(--space-4) var(--space-5);text-align:right;
  border-bottom:1px solid var(--border-hairline)}
.entry th{font-weight:700;color:var(--fg-primary)}
.entry tbody tr:last-child td{border-bottom:0}

@media (max-width:760px){
  .article-shell{padding-bottom:var(--space-12)}
  .entry h2{font-size:calc(var(--fs-h2) * .82)}
  .entry table{display:block;overflow-x:auto}
}


/* ======================== PROJECT SHOWCASE CARDS =======================
   The websima card, structurally: the whole card is ONE panel, the shots are
   composed inside it and bleed off its edge, a tab straddles the top edge with
   the category, the client sits bottom-start and the project name on a light
   tab bottom-end, and the dots ride a second tab on the bottom edge.

   Not copied: its colour. There every card has its own saturated brand
   gradient — the "رنگی رنگی" that was rejected. Every panel here is the same
   navy and the screenshots carry all the colour themselves.
   ====================================================================== */
.wgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-12) var(--space-8)}
.wgrid--three{grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-10) var(--space-6)}
@media (max-width:1100px){.wgrid--three{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:860px){.wgrid,.wgrid--three{grid-template-columns:minmax(0,1fr)}}

.pcard{position:relative}
/* Two stacked blocks — the name strip and the shot — with their own rounding
   and a hairline gap between them, rather than one panel with the name laid
   over the image. The aspect ratio moved to .pcard__panel with the shot. */
.pcard__link{
  position:relative;display:flex;flex-direction:column;gap:var(--space-2);
  transition:transform .4s var(--ease);
}
.pcard__link:hover{transform:translateY(-6px)}

.pcard__head{
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);
  padding:var(--space-4) var(--space-6);
  background:var(--bg-subtle);
  border-radius:var(--radius-lg);
  transition:background-color .3s var(--ease);
}
.pcard__link:hover .pcard__head{background:var(--bg-surface,#fff)}
/* The pill carries the category. Dark by default, amber on hover so the whole
   card answers the pointer as one object. */
.pcard__cat{
  flex:0 0 auto;
  padding:var(--space-2) var(--space-5);
  border-radius:var(--radius-full);
  background:var(--bg-panel-dark);color:var(--fg-on-dark);
  font-size:var(--fs-body-sm);font-weight:700;line-height:1.6;white-space:nowrap;
  transition:background-color .3s var(--ease),color .3s var(--ease);
}
.pcard__link:hover .pcard__cat{background:var(--accent-default);color:var(--fg-on-accent)}

.pcard__panel{
  position:relative;
  aspect-ratio:16/10;
  border-radius:var(--radius-lg);
  background:var(--bg-panel-dark);
  overflow:hidden;
  transition:box-shadow .4s var(--ease);
}
.pcard--compact .pcard__panel{aspect-ratio:4/3}
.pcard__link:hover .pcard__panel{box-shadow:var(--shadow-lift)}

/* --- the shot, filling almost the whole panel ------------------------
   Only a few percent of navy shows at the top and sides, and the shot runs
   off the bottom edge. A thick inset made the card read as a small picture
   floating on a large empty panel. */
.pcard__stage{position:absolute;inset:0}
/* Equal inset on all four sides. It used to be 4% top / 3.5% sides / 0 bottom,
   because the project name sat on a tab over the bottom of the shot and the
   art ran under it. The name lives in the strip above the panel now, so the
   flush bottom just read as a crop. Fixed px rather than a percentage: a
   percentage inset resolves against width for the sides and height for the
   top and bottom, so on a 16:10 panel "3.5%" is two different gaps. */
.pcard__slide{
  position:absolute;
  inset:16px;
  opacity:0;transition:opacity .5s var(--ease);
  border-radius:var(--radius-md);
  overflow:hidden;
}
.pcard--compact .pcard__slide{inset:12px}
.pcard__slide.is-active{opacity:1}
.pcard__slide img,.pcard__slide video{
  width:100%;height:100%;
  object-fit:cover;object-position:top center;
  display:block;
  transition:transform .7s var(--ease);
}
.pcard__link:hover .pcard__slide.is-active img{transform:scale(1.03)}

.pcard__play{
  position:absolute;inset-inline-end:var(--space-5);bottom:var(--space-10);z-index:4;
  width:40px;height:40px;border-radius:var(--radius-full);
  background:var(--accent-default);display:grid;place-items:center;
}
.pcard__play::after{
  content:"";border-style:solid;border-width:7px 11px 7px 0;
  border-color:transparent var(--fg-on-accent) transparent transparent;
}

/* the reference puts its favourite button in the top inline-start corner */
.pcard__aff{
  position:absolute;z-index:4;top:var(--space-5);inset-inline-start:var(--space-5);
  background:rgba(255,255,255,.92);color:var(--fg-primary);box-shadow:none;
}
.pcard__link:hover .pcard__aff{background:var(--accent-default);color:var(--fg-on-accent);transform:rotate(-45deg)}

/* --- the only label: the project name, on a light tab in the bottom
   inline-start corner, exactly as the reference does it -------------- */
.pcard__name{
  margin:0;min-width:0;
  font-size:var(--fs-body-lg);font-weight:800;line-height:1.5;
  color:var(--fg-primary);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.pcard--compact .pcard__name{font-size:var(--fs-body)}
.pcard--compact .pcard__head{padding:var(--space-3) var(--space-5)}
.pcard--compact .pcard__cat{font-size:12px;padding:var(--space-1) var(--space-4)}


/* dots opposite the name tab, so the two can never meet */
.pcard__dots{
  position:absolute;z-index:4;bottom:var(--space-5);inset-inline-end:var(--space-6);
  display:flex;gap:6px;align-items:center;
  padding:var(--space-2) var(--space-4);
  border-radius:var(--radius-full);
  background:rgba(0,0,0,.35);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
}
.pcard__dot{width:6px;height:6px;border-radius:var(--radius-full);
  background:rgba(255,255,255,.5);transition:background-color .3s var(--ease),width .3s var(--ease)}
.pcard__dot.is-active{width:18px;background:var(--accent-default)}

/* No hover on touch, so the stage never advances — dots would be decorative
   and misleading. The full set of shots is on the project page. */
@media (max-width:560px){
  .pcard__link{aspect-ratio:4/3}
  .pcard__dots{display:none}
  .pcard__name{font-size:var(--fs-body)}
}

/* --- dark panel variant (related projects) ----------------------------- */
.related-work{background:var(--bg-panel-dark);color:var(--fg-on-dark);border-radius:var(--radius-xl);
  margin-inline:var(--space-8);padding-block:var(--space-20)}
@media (max-width:860px){.related-work{margin-inline:0;border-radius:0}}
.related-work .h-sec{color:var(--fg-on-dark)}
.related-work .lede{color:var(--fg-on-dark-muted)}
/* on the dark panel the cards need a lighter ground to separate from it */
.related-work .pcard__link{background:rgba(255,255,255,.07);
  outline:1px solid var(--border-on-dark);outline-offset:-1px}

/* ======================= ARCHIVE OPENER + FILTER BAR =================== */
.pf-open{position:relative;padding-block:var(--space-16) var(--space-12);overflow:hidden;isolation:isolate}
.pf-open__inner{
  position:relative;z-index:3;
  display:grid;grid-template-columns:1fr 1fr;gap:var(--space-16);align-items:center;
}
@media (max-width:960px){.pf-open__inner{grid-template-columns:1fr;gap:var(--space-8)}}
.pf-open__render{position:relative;width:100%;max-width:520px;margin-inline:auto}
.pf-open__render video{width:100%;height:auto;display:block}
@media (max-width:960px){.pf-open__render{display:none}}   /* the copy leads on small screens */
.pf-open__copy{text-align:right}
.pf-open__h1{margin-bottom:var(--space-6)}

.pf-stats{display:flex;flex-wrap:wrap;gap:var(--space-10);margin-top:var(--space-10)}
.pf-stat{position:relative;padding-inline-start:var(--space-10)}
.pf-stat + .pf-stat{border-inline-start:1px solid var(--border-hairline)}
.pf-stat:first-child{padding-inline-start:0}
.pf-stat__n{display:block;font-family:var(--font-en-mono);font-size:34px;line-height:1;
  color:var(--accent-text);direction:ltr}   /* same accent token as .stat__n on the home page */
.pf-stat__l{margin-top:var(--space-3);font-size:var(--fs-body-sm);color:var(--fg-muted)}

.pf-section{padding-top:var(--space-8)}

/* One bar, not loose pills — the reference's single dark strip. */
.fbar{
  display:flex;align-items:center;gap:var(--space-6);
  padding:var(--space-2);
  border-radius:var(--radius-full);
  background:var(--bg-panel-dark);
  margin-bottom:var(--space-12);
}
.fbar__label{
  flex:0 0 auto;font-size:var(--fs-body-sm);color:var(--fg-on-dark-muted);
  padding-inline-start:var(--space-5);
}
 .fbar__links{display:flex;align-items:center;gap:var(--space-6);flex:1 1 auto;
    min-width:0;
    overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.fbar__links::-webkit-scrollbar{display:none}
.fbar__link{
  position:relative;flex:0 0 auto;
  display:inline-flex;align-items:center;gap:var(--space-2);
  padding-block:var(--space-3);
  font-size:var(--fs-body-sm);font-weight:600;
  color:var(--fg-on-dark-muted);
  white-space:nowrap;
  transition:color .25s var(--ease);
}
.fbar__link:hover{color:var(--fg-on-dark)}
.fbar__link.is-active{color:var(--fg-on-dark)}
.fbar__link.is-active::after{
  content:"";position:absolute;bottom:0;inset-inline:0;height:2px;
  background:var(--accent-default);border-radius:var(--radius-full);
}
.fbar__n{font-size:11px;opacity:.6}
.fbar__cta{flex:0 0 auto}
@media (max-width:860px){
  .fbar{flex-wrap:wrap;border-radius:var(--radius-lg);padding:var(--space-5) var(--space-6);gap:var(--space-4)}
  .fbar__label{display:none}
  .fbar__links{flex:1 1 100%}
  .fbar__cta{display:none}          /* the header already carries this CTA */
}

/* ========================= ALTERNATING FEATURE ROWS ==================== */
.frow{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--space-12);
  align-items:center;
  margin-bottom:var(--space-16);
}
.frow:last-child{margin-bottom:0}
.frow--flip .frow__text{order:2}
.frow--flip .frow__img{order:1}
@media (max-width:860px){
  .frow{grid-template-columns:1fr;gap:var(--space-8);margin-bottom:var(--space-12)}
  .frow--flip .frow__text,.frow--flip .frow__img{order:0}
}
/* Same Persian justification as .lede and .entry p — inter-word, no hyphens. */
.frow__text{color:var(--fg-secondary);line-height:1.95;
  text-align:justify;text-justify:inter-word;hyphens:none}
.frow__text p{margin:0 0 var(--space-5)}
.frow__text p:last-child{margin-bottom:0}
.frow__img{
  margin:0;border-radius:var(--radius-lg);overflow:hidden;
  background:var(--bg-subtle);box-shadow:var(--shadow-card);
}
.frow__img img{width:100%;height:auto;display:block}


/* the project hero carries no ghost wordmark, so it needs less top room */
.pf-hero{padding-block:var(--space-12) var(--space-10)}

/* Figma frame "Hero": a navy panel bleeding 40px off the leading edge with the
   project shot lying across it, and a 520px copy column on the trailing side.
   This replaces the earlier centred, full-width-copy treatment. */
.pf-hero{position:relative;overflow:hidden;padding-block:0;min-height:700px;background:var(--bg-page)}
.pf-hero__panel{
  position:absolute;top:0;inset-inline-end:-40px;z-index:0;
  width:620px;height:640px;background:var(--bg-panel-dark);
}
.pf-hero__inner{
  position:relative;z-index:2;min-height:700px;
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-10);
}
.pf-hero__copy{flex:0 0 520px;max-width:520px}
.pf-hero__h1{
  max-width:420px;
  font-size:clamp(38px,4.4vw,64px);line-height:1;letter-spacing:-.02em;
  text-align:right;
}
.pf-hero__g{display:block}
/* Measured: the second group steps 153px inward from the first. */
.pf-hero__g--step{margin-inline-start:clamp(0px,10vw,153px)}
.pf-hero__lede{max-width:none;margin-top:var(--space-6)}
.pf-hero__cta{display:flex;align-items:center;gap:var(--space-8);margin-top:var(--space-9)}
.pf-hero__more{font-size:var(--fs-body-sm);color:var(--fg-muted)}
.pf-hero__more:hover{color:var(--accent-text)}
.pf-hero .pfshot{flex:0 0 640px;max-width:640px;height:470px;margin-top:0;border-radius:16px}
@media (max-width:1100px){
  .pf-hero,.pf-hero__inner{min-height:0}
  .pf-hero{padding-block:var(--space-10)}
  .pf-hero__inner{flex-direction:column;align-items:stretch;gap:var(--space-9)}
  .pf-hero__copy,.pf-hero .pfshot{flex:1 1 auto;max-width:none}
  .pf-hero .pfshot{height:clamp(260px,52vw,420px)}
  .pf-hero__panel{width:100%;inset-inline-end:0;height:340px}
  .pf-hero__h1{max-width:none}
}

/* The about-section CTA inverts the signature button — navy body, amber
   capsule — so the page keeps exactly one primary amber button (the hero's). */
.btn--cta-inv{background:var(--bg-panel-dark);color:var(--fg-on-dark);box-shadow:0 0 0 3px rgba(45,64,89,.16)}
.btn--cta-inv:hover{background:var(--ink-800);box-shadow:0 0 0 5px rgba(45,64,89,.22)}
.btn--cta-inv .cap{background:var(--accent-default);color:var(--fg-on-accent)}

/* ---- project spec panel (Figma "panel / مشخصات و تکنولوژی‌ها") -------------
   One navy panel: spec rows in two columns on the trailing side, a hairline,
   then the stack as white pills. --spec-rows comes from PHP so the pairs fill
   column-major (down, then across) the way the frame reads. */
.specpanel{
  display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-9);margin-top:var(--space-10);padding:36px;
  border-radius:var(--radius-lg);
  background:var(--bg-panel-dark);color:var(--fg-on-dark);
}
.specpanel__list{
  margin:0;display:grid;gap:16px 40px;
  grid-template-rows:repeat(var(--spec-rows,3),auto);
  grid-auto-flow:column;grid-auto-columns:minmax(0,290px);
}
.specpanel__row{display:flex;flex-direction:column;gap:2px}
.specpanel__row dt{font-size:12px;line-height:1.75;color:var(--fg-on-dark-muted)}
.specpanel__row dd{margin:0;font-size:14px;font-weight:600;line-height:1.6;color:var(--fg-on-dark)}
.specpanel__rule{flex:0 0 1px;align-self:stretch;background:rgba(255,255,255,.16)}
/* LTR: the stack names are Latin, and a right-aligned Latin list reads
   back-to-front — the Figma frame starts at laravel on the left too. */
.specpanel__tech{display:flex;flex-wrap:wrap;gap:10px;max-width:500px;margin:0;padding:0;list-style:none;direction:ltr;justify-content:flex-start}
.specpanel__tech .chip{
  display:inline-flex;align-items:center;height:46px;padding-inline:24px;border:0;
  border-radius:var(--radius-full);
  background:var(--bg-surface);color:var(--bg-panel-dark);
  font-family:var(--font-en-body);font-size:14px;font-weight:500;line-height:1;
}
@media (max-width:1100px){
  .specpanel{flex-direction:column;align-items:stretch;gap:var(--space-8);padding:var(--space-8)}
  .specpanel__rule{height:1px;flex:0 0 1px;align-self:auto;width:100%}
  .specpanel__tech{max-width:none}
}
@media (max-width:560px){
  .specpanel__list{grid-auto-flow:row;grid-template-rows:none;grid-auto-columns:auto}
}

/* ---- about the project (Figma "02 دربارهٔ پروژه") --------------------------
   No photograph in this frame: the project's own domain, set huge and nearly
   invisible, IS the left half. The write-up sits right-aligned over it. */
.pfabout{position:relative;margin-top:var(--space-10);min-height:380px;overflow:hidden;
  display:flex;flex-direction:column;justify-content:center}
.pfabout__ghost{
  /* Physical `left`, not inset-inline-end: this element carries direction:ltr
     for the Latin wordmark, and logical insets resolve against the element's
     OWN direction — so the logical form pinned it to the right edge instead. */
  position:absolute;left:0;right:auto;top:50%;transform:translateY(-50%);z-index:0;
  font-family:var(--font-en-display);font-size:clamp(72px,13vw,190px);line-height:.85;
  letter-spacing:-.03em;color:var(--fg-primary);opacity:.055;
  white-space:nowrap;direction:ltr;pointer-events:none;user-select:none;
}
.pfabout__copy{position:relative;z-index:2;max-width:680px}
.pfabout__copy .eyebrow{margin-bottom:var(--space-4)}
/* A narrow measure on the heading is what breaks the accent word onto its own
   line, the way the frame stacks it. */
.pfabout__copy .h-sec{max-width:460px}
.pfabout__text{margin-top:var(--space-6);
  text-align:justify;text-justify:inter-word;hyphens:none}
.pfabout__text p{margin:0 0 var(--space-5)}
.pfabout__text p:last-child{margin-bottom:0}
.pfabout__copy .btn{margin-top:var(--space-8)}
@media (max-width:1100px){
  .pfabout{min-height:0}
  .pfabout__copy,.pfabout__copy .h-sec{max-width:none}
  .pfabout__ghost{font-size:clamp(56px,17vw,120px);opacity:.05}
}
}

/* --- project shot: scrolls top-to-bottom on hover -------------------------
   The panel is a fixed window; the image keeps its natural height and slides
   inside it. Travel and duration come from JS (--pf-shift / --pf-time) because
   they depend on the real image height — see [data-pfshot] in site.js. A shot
   with nothing to travel never gets .is-scrollable, so it simply sits still. */
.pfshot{
  position:relative;width:100%;max-width:1000px;
  margin-top:var(--space-10);
  height:clamp(280px,46vw,470px);
  overflow:hidden;
  border-radius:var(--radius-lg);
  background:var(--bg-panel-dark);
  box-shadow:var(--shadow-card);
}
.pfshot__img{
  display:block;width:100%;height:auto;
  transform:translateY(0);
  transition:transform var(--pf-time,4s) ease-in-out;
}
.pfshot.is-scrollable{cursor:ns-resize}
.pfshot.is-scrollable:hover .pfshot__img,
.pfshot.is-scrollable:focus-within .pfshot__img{transform:translateY(var(--pf-shift,0))}
@media (prefers-reduced-motion:reduce){
  .pfshot__img{transition:none}
  .pfshot.is-scrollable:hover .pfshot__img,
  .pfshot.is-scrollable:focus-within .pfshot__img{transform:none}
}

/* --- project gallery carousel (.pgal) ------------------------------------
   Replaces the static .svc-gallery grid on project pages. Arrows and dots
   reuse the work showcase's shapes so the two carousels read as one system. */
.pgal{position:relative;margin-top:var(--space-10)}
.pgal__swiper{overflow:hidden;border-radius:var(--radius-lg)}
.pgal__item{margin:0}
.pgal__item img{
  display:block;width:100%;height:clamp(180px,22vw,260px);
  object-fit:cover;object-position:top center;
  border-radius:var(--radius-lg);
  background:var(--bg-inset);
}
.pgal__nav{
  position:absolute;top:calc(50% - var(--space-8));z-index:3;transform:translateY(-50%);
  display:grid;place-items:center;width:48px;height:48px;
  border-radius:var(--radius-full);
  background:var(--fg-primary);color:var(--bg-page);
  border:0;cursor:pointer;
  transition:background-color .2s var(--ease),opacity .2s var(--ease);
}
.pgal__nav:hover{background:var(--accent-default);color:var(--fg-on-accent)}
.pgal__nav.is-disabled{opacity:.25;pointer-events:none}
/* RTL, same as .wshow__nav: next travels leftward. */
.pgal__nav--next{inset-inline-end:calc(var(--space-10) * -1)}
.pgal__nav--prev{inset-inline-start:calc(var(--space-10) * -1)}
.pgal__nav--prev svg{transform:scaleX(-1)}
.pgal--single .pgal__nav,.pgal--single .pgal__dots{display:none}
.pgal__dots{display:flex;justify-content:center;gap:0;margin-top:var(--space-8);flex-wrap:wrap}
.pgal__dots .swiper-pagination-bullet{
  width:26px;height:8px;margin:0;border-radius:var(--radius-full);
  background:var(--border-hairline);opacity:1;
  clip-path:inset(0 9px round var(--radius-full));
  transition:clip-path .2s var(--ease),background-color .2s var(--ease);
}
.pgal__dots .swiper-pagination-bullet-active{
  background:var(--accent-default);
  clip-path:inset(0 0 round var(--radius-full));
}
/* The arrows only clear the wrap on wide viewports; inside it below that. */
@media (max-width:1240px){
  .pgal__nav--next{inset-inline-end:var(--space-4)}
  .pgal__nav--prev{inset-inline-start:var(--space-4)}
}

/* ============================================================================
   Work showcase — front page  (.wshow)
   One project at a time in a split card: media on one side, the project's own
   facts on the other. Replaces the six-up grid; the grid's card (.pcard) is
   still used by the archive and the related-work rails, so it stays.
   Swiper drives it — the same instance type the testimonials use.
   ========================================================================= */
/* Narrower than the text column it sits under. At full wrap width the card ran
   1275px and the arrows were pushed out into the page margin; pulling it in
   gives them room and lets the section breathe, closer to the proportion the
   reference uses. */
.wshow{position:relative;margin-top:var(--space-12);max-width:min(100%,1120px);margin-inline:auto}
.wshow__swiper{overflow:hidden;border-radius:var(--radius-2xl)}

/* The card is a two-up grid with a hairline gutter, so the media reads as
   inset into the panel rather than bleeding to its edge — that inset is what
   makes the big radius look intentional instead of like a cropped photo. */
.wshow__card{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);
  padding:var(--space-2);
  background:var(--bg-surface,#fff);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-2xl);
  overflow:hidden;
}
.wshow__media{
  position:relative;overflow:hidden;
  border-radius:calc(var(--radius-2xl) - var(--space-2));
  background:var(--bg-page);
  aspect-ratio:4/3;
}
.wshow__media img,.wshow__media video{
  width:100%;height:100%;object-fit:cover;display:block;
}
.wshow__info{
  display:flex;flex-direction:column;justify-content:space-between;gap:var(--space-8);
  padding:var(--space-10);
}
.wshow__client{
  margin:0 0 var(--space-3);
  font-size:var(--fs-body-sm);color:var(--accent-text);
  font-weight:700;
}
.wshow__t{
  margin:0 0 var(--space-4);
  font-size:var(--fs-h3,28px);font-weight:900;line-height:1.35;
  color:var(--fg-primary);letter-spacing:-.01em;
}
.wshow__d{
  margin:0 0 var(--space-6);
  font-size:var(--fs-body);color:var(--fg-secondary);line-height:1.9;
}
.wshow__more{
  display:inline-flex;align-items:center;gap:var(--space-3);
  font-size:var(--fs-body);font-weight:800;color:var(--fg-primary);
  text-decoration:none;
}
/* The dark capsule holding the arrow is the primary CTA's signature; reusing
   it here ties the card back to the buttons instead of inventing a third
   affordance. */
.wshow__more-arw{
  display:grid;place-items:center;width:38px;height:38px;
  border-radius:var(--radius-full);
  background:var(--accent-default);color:var(--fg-on-accent);
  transition:background-color .2s var(--ease);
}
.wshow__more:hover .wshow__more-arw,
.wshow__more:focus-visible .wshow__more-arw{background:var(--fg-primary);color:var(--bg-page)}
.wshow__more:hover span:first-child{color:var(--accent-text)}

/* The facts row is the reference's stat strip, but the values are the post's
   own meta — durations and platform — not invented percentages. */
.wshow__facts{
  display:flex;gap:var(--space-10);margin:0;padding:var(--space-6) 0 0;list-style:none;
  border-top:1px solid var(--border-hairline);
}
.wshow__facts li{display:flex;flex-direction:column;gap:2px}
.wshow__fact-v{font-size:var(--fs-body-lg,20px);font-weight:900;color:var(--fg-primary)}
.wshow__fact-l{font-size:var(--fs-body-sm);color:var(--fg-muted)}

/* Arrows sit clear of the card. Both glyphs are the same ← so the "next"
   one is mirrored. */
.wshow__nav{
  position:absolute;top:50%;z-index:3;transform:translateY(-50%);
  display:grid;place-items:center;width:48px;height:48px;
  border-radius:var(--radius-full);
  background:var(--fg-primary);color:var(--bg-page);
  border:0;cursor:pointer;
  transition:background-color .2s var(--ease),opacity .2s var(--ease);
}
.wshow__nav:hover{background:var(--accent-default);color:var(--fg-on-accent)}
.wshow__nav.is-disabled{opacity:.25;pointer-events:none}
/* RTL: forward travel is leftward, so "next" belongs on the LEFT pointing left
   and "prev" on the RIGHT pointing right. The sides were swapped — each button
   sat opposite the direction its own arrow pointed. inset-inline-end resolves
   to the left edge in RTL, inline-start to the right. The shared icon is ←, so
   only prev is mirrored. */
.wshow__nav--next{inset-inline-end:calc(var(--space-12) * -1)}
.wshow__nav--prev{inset-inline-start:calc(var(--space-12) * -1)}
.wshow__nav--prev svg{transform:scaleX(-1)}

/* The active dot grows into a pill without animating width. Each bullet's box
   is always 26px — the pill's full size — and clip-path reveals only the middle
   8px while it is inactive. inset() takes a `round` argument, so the clipped
   shape keeps its own rounded ends and reads as a circle, not a cropped bar;
   scaling instead would squash the radius to 1.2px and give a rounded square.
   clip-path is not a layout property, so nothing reflows — and unlike the width
   transition this replaces, the neighbouring dots no longer shift sideways
   every time the slider pages. Gap and margin are zero because the fixed box
   already carries the spacing. */
.wshow__dots{display:flex;justify-content:center;gap:0;margin-top:var(--space-8);flex-wrap:wrap}
.wshow__dots .swiper-pagination-bullet{
  width:26px;height:8px;margin:0;border-radius:var(--radius-full);
  background:var(--border-hairline);opacity:1;
  clip-path:inset(0 9px round var(--radius-full));
  transition:clip-path .2s var(--ease),background-color .2s var(--ease);
}
.wshow__dots .swiper-pagination-bullet-active{
  background:var(--accent-default);
  clip-path:inset(0 0 round var(--radius-full));
}

/* Below the split, the two halves stack and the arrows come inside the flow —
   at 900 the info column was down to ~280px and the title broke every word. */
@media (max-width:900px){
  .wshow__card{grid-template-columns:1fr}
  .wshow__info{padding:var(--space-8)}
  .wshow__media{aspect-ratio:16/10}
  .wshow__nav{position:static;transform:none;display:none}
  .wshow__facts{gap:var(--space-6)}
}
@media (max-width:600px){
  .wshow__facts{flex-wrap:wrap;gap:var(--space-5) var(--space-8)}
  .wshow__info{padding:var(--space-6)}
}

/* ============================================================================
   Process timeline — service pages  (.ptl)
   [mark][spine][body], with the two outer cells swapping every other row so the
   numbers zig-zag down a fixed centre line.
   ========================================================================= */
/* gap:0 on purpose — any gap between rows cuts the spine into segments, and a
   broken line is worse than no line. Rows are separated by their own padding. */
.ptl{
  position:relative;
  display:flex;flex-direction:column;gap:0;
  margin:var(--space-12) 0 0;padding:0;list-style:none;
}

/* The travelled part of the spine. One element for the whole list rather than a
   fragment per row, so the fill is continuous and there is nothing to keep in
   sync. left:50% lands exactly on the dots: the rows are full width with
   symmetric padding and the middle grid column is centred, so both resolve to
   the same axis. Positioned pseudo-elements paint after the rows' backgrounds
   but before .ptl__dot (which is z-index:1), so the fill covers the hairline
   track and still passes under each dot. Height comes from JS. */
.ptl::after{
  content:"";position:absolute;pointer-events:none;
  left:50%;transform:translateX(-50%);
  top:var(--ptl-start,0px);
  height:var(--ptl-progress,0px);
  width:2px;border-radius:var(--radius-full);
  background:var(--accent-default);
}
.ptl__row{
  display:grid;
  grid-template-columns:1fr 160px 1fr;
  align-items:center;
  min-height:160px;
  padding:var(--space-6) var(--space-10);
  border-radius:var(--radius-2xl);
  background:transparent;
  transition:background-color .35s var(--ease);
}
/* The row in view lifts onto the surface. Everything else stays on the page
   ground, so the eye has one place to be — that highlight is the whole point
   of the pattern, not decoration. */
.ptl__row.is-active{background:var(--bg-surface,#fff)}

/* All three cells are placed explicitly. Leaving the spine on auto-placement
   was the bug: with the two outer cells pinned, auto put the spine in column 1
   on flipped rows, so the dot jumped 338px sideways and the line could never
   join up. RTL note: column 1 is the RIGHT column.
   Body text always hugs the spine, which is what reads as one centre axis. */
/* grid-row:1 is load-bearing, not tidiness. Grid auto-placement is sparse: its
   cursor never moves backwards, so on a flipped row (mark pinned to column 3,
   body to column 1) the spine and body could not fit after it and were pushed
   into implicit rows 2 and 3. The spine then sized to its dot — 18px instead of
   the row's 220 — and the line broke at every flipped step. Pinning the row
   puts all three cells back on one line. */
.ptl__row .ptl__mark {grid-column:1;grid-row:1;justify-content:flex-end}
.ptl__row .ptl__spine{grid-column:2;grid-row:1}
.ptl__row .ptl__body {grid-column:3;grid-row:1;text-align:start}
.ptl__row--flip .ptl__mark{grid-column:3;grid-row:1;justify-content:flex-start}
.ptl__row--flip .ptl__body{grid-column:1;grid-row:1;text-align:end}

.ptl__mark{display:flex;align-items:center;gap:var(--space-4);justify-content:flex-end}
/* The tile carries the step number. The reference puts a per-step icon here,
   but this theme ships three icons and ↖ already means "opens something"
   everywhere else — repeating it six times would say nothing. The number is
   the one thing every step genuinely has. */
.ptl__badge{
  display:grid;place-items:center;width:72px;height:72px;
  border-radius:var(--radius-xl);
  background:var(--bg-panel-dark,#2D4059);color:#fff;
  font-size:24px;font-weight:900;line-height:1;
  transition:background-color .35s var(--ease),color .35s var(--ease);
}
.ptl__row.is-active .ptl__badge{background:var(--accent-default);color:var(--fg-on-accent)}

/* The spine is drawn on the cell, not on a wrapper, so it spans the full row
   height and the rows butt together into one unbroken line. */
.ptl__spine{position:relative;align-self:stretch;display:grid;place-items:center}
/* The line runs past the cell's content box by exactly the row's vertical
   padding, so consecutive rows join instead of leaving a 24px break at every
   step. Keep this in sync with .ptl__row's padding-block. */
.ptl__spine::before{
  content:"";position:absolute;left:50%;width:1px;
  top:calc(var(--space-6) * -1);bottom:calc(var(--space-6) * -1);
  transform:translateX(-50%);
  background:var(--border-hairline);
}
.ptl__row:first-child .ptl__spine::before{top:50%}
.ptl__row:last-child  .ptl__spine::before{bottom:50%}
.ptl__dot{
  position:relative;z-index:1;
  width:18px;height:18px;border-radius:var(--radius-sm);
  background:var(--bg-page);border:1px solid var(--border-hairline);
  transition:background-color .35s var(--ease),border-color .35s var(--ease),transform .35s var(--ease);
}
/* A dot the fill has already reached stays lit, so the line never runs past a
   hollow marker. The row you are actually on is the one that also scales up. */
.ptl__row.is-passed .ptl__dot{background:var(--accent-default);border-color:var(--accent-default)}
.ptl__row.is-active .ptl__dot{background:var(--accent-default);border-color:var(--accent-default);transform:scale(1.25)}

.ptl__body{text-align:end}
.ptl__t{margin:0 0 var(--space-3);font-size:var(--fs-body-lg,20px);font-weight:900;line-height:1.5;color:var(--fg-primary)}
.ptl__d{margin:0;font-size:var(--fs-body);line-height:1.9;color:var(--fg-secondary)}

/* One column below the split: the spine and the zig-zag both stop earning
   their space once the row is narrower than the text it holds. The number
   stays, because the order is the content. */
/* Every cell's placement is restated here, flipped rows included. The desktop
   rules pin grid-row:1 to hold the spine's line together; carrying that pin
   into a one- or two-column layout without also restating the columns dropped
   the badge on top of the paragraph. The rows get their gap back too, since
   the spine that used to separate them is gone. */
@media (max-width:900px){
  .ptl{gap:var(--space-2)}
  .ptl__row,.ptl__row--flip{
    grid-template-columns:auto 1fr;
    gap:var(--space-5);
    align-items:start;
    min-height:0;
    padding:var(--space-6);
  }
  .ptl__row .ptl__mark,.ptl__row--flip .ptl__mark{grid-column:1;grid-row:1;justify-content:flex-start}
  .ptl__row .ptl__body,.ptl__row--flip .ptl__body{grid-column:2;grid-row:1;text-align:start}
  .ptl__row .ptl__spine,.ptl__row--flip .ptl__spine{display:none}
  .ptl::after{display:none}        /* no spine below the split, so no fill */
  .ptl__badge{width:56px;height:56px;border-radius:var(--radius-lg);font-size:20px}
}
@media (max-width:600px){
  .ptl__row,.ptl__row--flip{grid-template-columns:1fr;gap:var(--space-4)}
  .ptl__row .ptl__mark,.ptl__row--flip .ptl__mark{grid-column:1;grid-row:1}
  .ptl__row .ptl__body,.ptl__row--flip .ptl__body{grid-column:1;grid-row:2}
}

/* ============================ STEP RAIL ================================
   The soulera "journey" shape: numbered discs on one horizontal line with
   each step's copy in a column beneath its disc. RTL, so the rail reads
   right to left and the travelled part of the line fills from the right. */
.rail{
  display:grid;
  grid-template-columns:repeat(var(--rail-n,4),1fr);
  gap:var(--space-8);
  margin:var(--space-12) 0 0;padding:0;list-style:none;
  position:relative;
}
/* One line behind every disc rather than a segment per gap: a segment set
   drifts out of true the moment a step's title wraps to a second line. */
.rail::before{
  content:"";position:absolute;z-index:0;
  top:26px;                       /* half the disc */
  inset-inline:calc(100% / var(--rail-n,4) / 2);
  height:1px;background:var(--border-hairline);
}
/* The travelled part, filled by scroll. --rail-p runs 0 to 1 and is set by JS;
   the width is that fraction of the span between the first and last disc, so
   the fill starts under disc one and ends under the last. Transitioned rather
   than snapped, so a fast scroll still reads as the line drawing itself. */
.rail::after{
  content:"";position:absolute;z-index:0;
  top:26px;
  inset-inline-start:calc(100% / var(--rail-n,4) / 2);
  /* Full length always, revealed with scaleX rather than by animating width.
     The line is a 1px rectangle with no radius, so scaling it is geometrically
     identical to resizing it — and it is composited instead of laying out.
     transform-origin is the right edge because this rail only ever renders
     inside .section--rtl; mirror it if the theme ever ships an LTR variant. */
  width:calc(100% - 100% / var(--rail-n,4));
  height:1px;background:var(--fg-primary);
  transform:scaleX(var(--rail-p,0));
  transform-origin:right center;
  transition:transform .25s linear;
}
/* A disc the fill has reached lights up. The first is lit from the start —
   the line begins at it, so it is never "ahead" of the progress. */
.rail__step.is-lit .rail__disc{
  background:var(--accent-default);border-color:var(--accent-default);color:var(--fg-on-accent);
}
@media (prefers-reduced-motion:reduce){
  /* No scroll choreography: show the whole path at once. */
  .rail{--rail-p:1}
  .rail::after{transition:none}
}
.rail__step{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;text-align:center}
.rail__disc{
  display:grid;place-items:center;
  transition:background-color .3s var(--ease),border-color .3s var(--ease),color .3s var(--ease);
  width:52px;height:52px;border-radius:var(--radius-full);
  background:var(--bg-surface,#fff);
  border:1px solid var(--border-hairline);
  box-shadow:var(--shadow-card);
  font-size:var(--fs-body);font-weight:800;color:var(--fg-primary);
  margin-bottom:var(--space-8);
}
/* Fallback when JS never runs: the first step still reads as the start. */
.rail:not([data-rail-ready]) .rail__step:first-child .rail__disc{background:var(--accent-default);border-color:var(--accent-default);color:var(--fg-on-accent)}
.rail__t{margin:0 0 var(--space-3);font-size:var(--fs-body-lg);font-weight:900;line-height:1.5;color:var(--fg-primary)}
.rail__d{margin:0;font-size:var(--fs-body);line-height:1.9;color:var(--fg-secondary);max-width:34ch}

.steps__eyebrow{
  margin:0 0 var(--space-3);
  font-size:var(--fs-body-sm);font-weight:700;
  color:var(--accent-text);           /* accent as TEXT must use accent/text */
}

/* Two-up, then stacked. The line is dropped rather than redrawn per row: a
   rail that wraps is no longer one rail. */
@media (max-width:1000px){
  .rail{grid-template-columns:repeat(2,1fr);gap:var(--space-10) var(--space-8)}
  .rail::before,.rail::after{display:none}
}
@media (max-width:600px){
  .rail{grid-template-columns:1fr;gap:var(--space-10)}
  .rail__step{align-items:flex-start;text-align:start}
  .rail__d{max-width:none}
}

/* ============================ PAGINATION ===============================
   mango_pagination() has always emitted <nav class="pager"><ul><li>…, but the
   stylesheet never carried a rule for it, so the archive's page links rendered
   as a bare bulleted list. */
.pager{margin-top:var(--space-16)}
.pager ul{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:var(--space-2);margin:0;padding:0;list-style:none;
}
.pager li{display:flex}
.pager .page-numbers{
  display:grid;place-items:center;
  min-width:44px;height:44px;padding:0 var(--space-4);
  border-radius:var(--radius-full);
  background:var(--bg-surface,#fff);
  border:1px solid var(--border-hairline);
  color:var(--fg-secondary);
  font-size:var(--fs-body);font-weight:700;line-height:1;
  text-decoration:none;
  transition:background-color .22s var(--ease),color .22s var(--ease),border-color .22s var(--ease);
}
.pager a.page-numbers:hover{background:var(--bg-subtle);color:var(--fg-primary)}
.pager .page-numbers.current{
  background:var(--accent-default);border-color:var(--accent-default);
  color:var(--fg-on-accent);
}
/* prev/next carry words, not a glyph, so they get room to breathe. */
.pager .prev.page-numbers,
.pager .next.page-numbers{padding:0 var(--space-6)}
.pager .page-numbers.dots{background:transparent;border-color:transparent}
@media (max-width:600px){
  .pager{margin-top:var(--space-12)}
  .pager .page-numbers{min-width:40px;height:40px;font-size:var(--fs-body-sm)}
}
