:root{
  /* Palette moved to css/tokens.css — see that file for the single source of truth.
     These three are the article's own content colours, so they stay here, but
     they are derived from the theme rather than being a second palette: they
     used to be the old cold set (#C7D0DE / #D9E2EF on #0E0F13), which is why the
     post pages read noticeably bluer than the homepage even after everything
     else was corrected. */
  color-scheme:dark;
  --strong-text:#FFFFFF;   /* b/strong in prose: brighter than --paper */
  --hover-text:#F5F3F0;    /* prose link on hover: full-strength text */
  --code-bg:#0C0A09;       /* inline code and pre: recessed below the card */
}
html[data-theme="light"]{
  color-scheme:light;
  --strong-text:#000000;   /* stronger than --paper, on a white page */
  --hover-text:#1A1A1A;
  --code-bg:#F4F2EE;       /* neutral, matches --panel-2 — the old #EAE3D7
                              beige block sat on the white page like a stain */
}
*{box-sizing:border-box;}
html{margin:0; padding:0;}
body{margin:0; padding:0;}
/* main below pads its own bottom edge by --foot-fixed-h, so the last paragraph
   clears the fixed footer. This sheet used to reset html,body to padding:0,
   which is harmless now that the clearance lives on main. */
html{-webkit-tap-highlight-color:transparent;}
a,button,input,textarea,summary,label{touch-action:manipulation;}

/* ======================= skip link ======================= */
.skip-link{
  position:fixed; z-index:200; inset-inline-start:16px; top:16px;
  /* --on-accent, not #fff. In the dark theme --grad is a pale cyan, so a hard
     white label sat at about 1.5:1 — on the element that only appears when
     someone tabs to it, which is exactly when it has to be readable. */
  background-color:var(--grad); color:var(--on-accent); padding:10px 18px; border-radius:8px; font-size:13.5px; font-weight:600;
  opacity:0; pointer-events:none; transform:translateY(-12px); transition:opacity .2s ease, transform .2s ease;
}
.skip-link:focus{opacity:1; pointer-events:auto; transform:translateY(0);}
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible{outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--signal) 40%, transparent); border-radius:4px;}
@media (prefers-reduced-motion: reduce){html{scroll-behavior:auto;} .skip-link{transition:none;}}
html{scroll-behavior:smooth; scrollbar-width:none; -ms-overflow-style:none; font-synthesis-weight:none; -webkit-text-size-adjust:100%; text-size-adjust:100%;}
html::-webkit-scrollbar{display:none; width:0; height:0;}
body{background-color:var(--ink);   color:var(--paper); line-height:1.9; -webkit-font-smoothing:antialiased; font-optical-sizing:auto; text-rendering:optimizeLegibility;}
/* Body family comes from --font-body in css/tokens.css. These two rules used
   to hardcode 'Vazirmatn' for fa and 'Inter' for en, overriding the token and
   pinning every article to two families that were never self-hosted. */
html[lang="fa"] body{font-synthesis:none;}
html[lang="en"] body{line-height:1.75;}
/* --band-h tall, matching the header pill on index.html and blog.html and the
   fixed footer card. All three bands are 60px and share one width token. The
   controls keep min-height:44px for WCAG 2.5.8 — the height comes from the band,
   not from shrinking the target. */
/* The 20px above the bar is the mirror of the footer's 20px below its card. At
   top:0 the article reserved no space at all, so its header band sat against the
   viewport edge while the other pages' floated 20px down.
   against the footer's 74px and the two read as different sizes even though the
   bar itself was the same height. body gets the 20px so the band exists in the
   document and the bar can still stick to the top when it reaches it. */
body{padding-top:20px;}
/* The article bar is centred at --band-w like every other bar, and the floating
   action button that aligns to the band's edge needs the page gutter as its
   floor. This was 0px, so below --band-w the max() in the FAB's inset resolved
   to zero and the button sat flush against the viewport edge with no clearance
   at all — the one place on the site where a control touched the bezel. */
:root{--head-inset:var(--gutter);}
/* width:var(--band-w) like every other bar, so the footer card matches this
   page's too. It was edge to edge — no width of its own — which is why this
   page's two bands never lined up. max-width:100% keeps it inside the viewport
   once --band-w goes to 100% below 1128px. The 1px border is inside the width
   under box-sizing, so the bar and the card are the same size including it. */
/* The same pill as .topbar-inner on index.html and blog.html, and it has to be
   the same pill rather than a square band. All three headers are the same
   element measured 1168x60 at the same x, they share one glass film, and they
   were still visibly three different objects because this one had square
   corners: the radius is what makes a frosted band read as a floating pill, and
   without it the article header was the one band that looked pasted onto the
   page. The border is a full 1px box for the same reason — a border-bottom
   alone draws a line under a pill whose sides and top are meant to be glass. */
/* The header is a wrapper plus a band, which is the structure header.topbar /
   .topbar-inner and footer / .foot-inner already use on the other pages, and the
   reason is the gutter.

   .bar is the sticky full-width wrapper: it takes padding:0 var(--gutter) and no
   width of its own, so it reserves the page inset. The band inside it is
   --band-w, and it is the band that carries the glass, the border, the radius and
   the shadow.

   These were the same element, so the band was both the wrapper and the content,
   and one of the two had to give. It gave the inset: at 320px the pill painted
   edge to edge from x=0 to x=320 while the text it framed ran from x=20 to x=300,
   so the header stuck out past the text by the full gutter on a phone. Above
   --band-w the bug is invisible, because a centred max-width:100% band and a
   gutter-padded wrapper resolve to the same box — which is why the article page
   looked correct on a desktop and wrong on a mobile. */
.bar{position:sticky; top:calc(20px + env(safe-area-inset-top)); z-index:10; width:100%; margin-inline:auto; padding:0 var(--gutter);}
/* The band. --band-w resolves to the reading measure on this page, because
   blog-post.css re-states --page-w and --gutter above, so the pill frames the
   prose instead of overhanging it.

   height, not min-height. A minimum is only a floor: the bar grew past it across
   widths as the language select wrapped, so on an article the header and the
   footer card came out different sizes. An explicit height with box-sizing pins
   it at --band-h, and the 1px border is inside that box, so the bar measures the
   same 60px as the footer card. The controls keep their own min-height:44px for
   WCAG 2.5.8 and sit centred inside. */
.bar-inner{width:var(--band-w); max-width:100%; margin:0 auto; padding:0 20px; height:var(--band-h); box-sizing:border-box; display:flex; align-items:center; justify-content:space-between; gap:12px; overflow:hidden; background-color:var(--header-bg); backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur); border-radius:999px; border:1px solid var(--header-border); box-shadow:var(--header-shadow);}
.brand{color:var(--paper); text-decoration:none; font-weight:700; font-size:17px;}
.brand span{color:var(--signal);}
.back{display:inline-flex; align-items:center; min-height:44px; font-size:13.5px; color:var(--signal); border:1px solid var(--signal-dim); border-radius:999px; padding:8px 16px; text-decoration:none; white-space:nowrap;}
.back:hover{border-color:var(--signal);}
.bp-lang{background-color:var(--header-bg); border:1px solid var(--line); border-radius:999px; color:var(--paper); font-family:inherit; font-size:13px; min-height:44px; padding:0 14px; cursor:pointer; max-width:150px;}
.bp-lang:hover{border-color:var(--signal);}
@media(max-width:480px){
  /* Height and gap shrink, never the bar's height — that has to stay equal to
     the footer card's at every width. Only the horizontal padding and the gap
     between the two controls are reduced here. */
  /* Height is not restated here: it is --band-h on .bar and .bar-inner at every
     width, set once above. Only the padding and the gap change. */
  .bar-inner{padding:0 12px; gap:8px;}
  .back{padding:6px 10px; font-size:12.5px;}
  .bp-lang{padding:0 8px; font-size:12.5px; max-width:96px;}
  .brand{font-size:14px;}
}
@media(max-width:380px){ .brand span{display:none;} }
/* The bottom padding is the fixed footer's full band: the card plus the 20px
   inset below it. Not the card alone — the document's last pixel lines up with
   the bottom of the viewport when scrolled to the end, so the band covers that
   many pixels of the viewport and the padding has to match all of it or the
   closing paragraph sits 20px under the card. */
/* Reading pages adopt the narrower measure by re-stating the two numbers the
   whole system already derives from. Nothing below this line is article-specific
   geometry as a result: --band-w resolves to 640px on its own, so the article
   header pill and the article footer card land on the same edges as the prose
   without either component having a rule about reading pages.

   The page set --page-w:820px with 20px of padding, so the text column came out
   780px while --band-w stayed 1168px — the pill ran from x=136 to x=1304 around
   text that ran from x=330 to x=1110, overhanging the prose by 194px on each
   side. At 16px that column also held 22 words per line against a comfortable
   12-16, so the fix is one of width rather than font size: shrinking the type to
   buy back line length would have made the whole page smaller to correct one
   element of it. */
:root{--page-w:var(--read-page-w); --gutter:var(--read-gutter);}
/* The 48px top padding is the top step of the spacing scale, so it names the
   step rather than repeating the number. The bottom is not a spacing step: it
   is the fixed footer's height, reserved so the last line of an article clears
   the bar that sits over the page. */
main{max-width:var(--page-w); margin:0 auto; padding:var(--sp-12) var(--gutter) var(--foot-fixed-h, 88px);}
.eyebrow{display:inline-flex; align-items:center; gap:8px; font-size:12px; color:var(--signal); border:1px solid var(--signal-dim); border-radius:999px; padding:5px 12px; margin-bottom:18px;}
.eyebrow::before{content:""; width:6px; height:6px; border-radius:50%; background-color:var(--signal);}
h1{font-size:clamp(1.5625rem, 1rem + 2.4vw, 2.5rem); line-height:1.3; margin:0 0 8px; letter-spacing:0; font-weight:700;}
:lang(en) h1{letter-spacing:-.015em;}
h1,h2,h3,.faq-heading,.rel-title,.faq-item summary{text-wrap:balance;}
.date{color:var(--muted-2); font-size:13.5px; margin:0 0 var(--sp-8); padding-bottom:var(--sp-6); border-bottom:1px solid var(--line);}
.prose p{margin:0 0 var(--sp-6); color:var(--muted); text-wrap:pretty; orphans:2; widows:2;}
.prose p:last-child{margin-bottom:0;}
.next-links{margin-top:var(--sp-8); padding-top:var(--sp-4); border-top:1px solid var(--line); display:flex; flex-wrap:wrap; gap:10px; justify-content:space-between; font-size:14px;}
.next-links a{color:var(--signal); text-decoration:none;}
/* Footer styles moved to css/footer.css — one card for all three page types.
   The article pages used to get a narrower 820px card via .foot-narrow; that
   rule is gone and the card is --band-w here too, so the footer band frames the
   article page exactly as it frames the listing. The class has been removed from
   the 24 article pages as well. */
@media (prefers-reduced-motion: reduce){*{animation:none !important;}}

/* The per-post cover image was removed on purpose: `image` is gone from
   data/content-blog.json and no article renders an <img class="hero"> any more,
   so its rule went with it. Re-add a `image` field to a post to bring it back. */
/* The heading scale inside the article. A section head (h2) opens with the
   section step and closes with a tight step, so a heading binds to the text it
   introduces and stands off from what came before; a subsection (h3) is a
   single step smaller on each side because it lives inside a section rather
   than starting one. The previous values were 36/14 and 26/12 — close enough to
   look deliberate and inconsistent enough that the two heading levels did not
   read as a relationship. */
.prose h2{font-size:clamp(1.1875rem, 0.25rem + 1.7vw, 1.5rem); font-weight:700; margin:var(--sp-8) 0 var(--sp-3); line-height:1.45;}
.prose h3{font-size:17.5px; font-weight:700; margin:var(--sp-6) 0 var(--sp-3); line-height:1.45;}
.prose ul, .prose ol{margin:0 0 var(--sp-6); padding-inline-start:26px;}
.prose li{margin-bottom:var(--sp-2); line-height:1.95;}
.prose strong{color:var(--strong-text); font-weight:800;}
.prose a{color:var(--signal); text-decoration:underline; text-decoration-color:var(--signal-dim); text-underline-offset:5px;}
.prose a:hover{color:var(--hover-text);}
.prose pre{background-color:var(--code-bg); border:1px solid var(--line); border-radius: var(--radius-md); padding:16px 18px; margin:0 0 22px; overflow-x:auto; direction:ltr; text-align:left; font-size:13.5px; line-height:1.75;}
.prose code{font-family:var(--mono);}
.prose p code, .prose li code{background-color:var(--panel); border:1px solid var(--line); border-radius:6px; padding:1px 6px; font-size:.9em; direction:ltr;}

.faq-heading{font-size:clamp(1.1875rem, 0.25rem + 1.7vw, 1.5rem); font-weight:700; margin:var(--sp-8) 0 var(--sp-4);}
.faq-item{border:1px solid var(--line); border-radius: var(--radius-md); background-color:var(--panel); margin-bottom:12px; overflow:hidden;}
.faq-item summary{cursor:pointer; list-style:none; padding:16px 18px; font-weight:700; font-size:15px; color:var(--paper); display:flex; align-items:center; gap:12px;}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary::before{content:"+"; color:var(--signal); font-weight:800; width:20px; height:20px; border-radius:6px; border:1px solid var(--signal-dim); display:flex; align-items:center; justify-content:center; flex:none; transition:transform .2s;}
.faq-item[open] summary::before{transform:rotate(45deg);}
.faq-item .faq-answer{padding:0 18px 18px; color:var(--muted); font-size:14.5px; line-height:1.95;}
.faq-item .faq-answer p{margin:0;}

.related{margin-top:var(--sp-8); padding-top:var(--sp-6); border-top:1px solid var(--line);}
.related h2{font-size:18px; margin-bottom:var(--sp-4);}
.related-grid{display:grid; gap:14px;}
@media(min-width:600px){.related-grid{grid-template-columns:repeat(3, 1fr);}}
.rel-card{display:flex; flex-direction:column; gap:8px; background:linear-gradient(165deg, var(--panel), var(--rel-bg)); border:1px solid var(--line); border-radius: var(--radius-md); overflow:hidden; padding:14px 0; text-decoration:none; transition:border-color .2s, transform .2s;}
.rel-card:hover{border-color:var(--signal); transform:translateY(-3px);}
.rel-card .rel-title{font-size:13.5px; font-weight:700; padding:0 14px; line-height:1.6; color:var(--paper); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; line-clamp:3;}
.rel-card .rel-date{font-size:11px; color:var(--muted-2); padding:0 14px;}

/* ======================= reading progress bar ======================= */
.progress-bar{
  position:fixed; top:0; left:0; right:0; height:3px; z-index:100;
  background-color:var(--grad); transform-origin:left center; transform:scaleX(0);
  pointer-events:none; will-change:transform;
}
[dir="rtl"] .progress-bar{ transform-origin:right center; }

.prose h2,.prose h3{scroll-margin-top:120px;}
/* ======================= table of contents ======================= */
.toc{
  margin:0 0 var(--sp-8); padding:var(--sp-4) var(--sp-6); background-color:var(--toc-bg);
  border:1px solid var(--line); border-radius: var(--radius-md);
}
.toc-title{display:block; font-size:12px; font-weight:800; color:var(--muted-2); margin-bottom:12px; user-select:none;}
.toc-link{display:flex; align-items:center; min-height:40px; padding:7px 0; font-size:13.5px; line-height:1.7; color:var(--muted); border-inline-start:2px solid var(--line); padding-inline-start:14px; text-decoration:none; transition:color .15s, border-color .15s;}
.toc-link:hover{color:var(--signal);}
.toc-link.active{color:var(--signal); border-inline-start-color:var(--signal); font-weight:700;}
.toc-l3{padding-inline-start:30px; font-size:12.8px;}

/* ======================= share ======================= */
.share{display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin:var(--sp-2) 0 var(--sp-1); padding:var(--sp-4) 0; border-block:1px solid var(--line);}
.share-label{font-size:13px; font-weight:700; color:var(--muted-2);}
.share-btns{display:flex; align-items:center; gap:8px; flex-wrap:wrap;}
.share-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px; min-height:40px; padding:9px 13px; border-radius:10px;
  background-color:var(--panel); border:1px solid var(--line); color:var(--paper); cursor:pointer;
  font-family:inherit; font-size:12.5px; font-weight:600; text-decoration:none;
  transition:transform .12s, border-color .2s, color .2s;
}
.share-btn:hover{border-color:var(--signal); color:var(--signal);}
.share-btn:active{transform:scale(.95);}
.share-btn svg{flex:none; color:var(--signal);}
.share-btn.copied{color:var(--success); border-color:var(--success);}
.share-btn.copied svg{color:var(--success);}

/* Same treatment as the listing's share bar in css/blog.css, and for the same
   reason: these buttons carry a text label, so they take the brand hue on the
   border and the glyph rather than a solid fill behind the word "Telegram". */
.share-btn[data-platform]{color:var(--brand-ink); border-color:color-mix(in srgb, var(--brand) 34%, var(--line));}
.share-btn[data-platform] svg{color:var(--brand-ink);}
.share-btn[data-platform]:hover,.share-btn[data-platform]:focus-visible,.share-btn[data-platform]:active{border-color:var(--brand); color:var(--brand-on); background-color:var(--brand);}
.share-btn[data-platform]:hover svg,.share-btn[data-platform]:focus-visible svg,.share-btn[data-platform]:active svg{color:var(--brand-on);}
.share-btn[data-platform].copied,.share-btn[data-platform].copied svg{color:var(--success); border-color:var(--success);}

/* ======================= prev / next post nav ======================= */
.post-nav{display:flex; justify-content:space-between; gap:12px; margin-top:var(--sp-8);}
.post-nav-link{
  flex:1; max-width:49%; display:flex; flex-direction:column; gap:5px;
  background-color:var(--panel); border:1px solid var(--line); border-radius: var(--radius-md); padding:14px 16px;
  text-decoration:none; transition:border-color .2s, background-color .2s;
}
.post-nav-link:hover{border-color:var(--signal); background-color:var(--rel-bg);}
.nav-label{font-size:11px; font-weight:700; color:var(--signal);}
.nav-title{font-size:13.5px; font-weight:700; line-height:1.6; color:var(--paper); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;}
.nav-slot{flex:1;}

@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  *{transition:none !important;}
}