/* Typography lives in css/fonts.css (Dana) and css/tokens.css (the stack).
   This file used to declare Gofteh and IRANYekanX as its own families, so the
   blog was the one surface that could disagree with the homepage about
   typeface — and both were single static weights announced as 400-900, so
   every weight collapsed to regular. See the longer note in css/main.css. */
  /* The blog is a separate page with its own stylesheet, which let it drift into
     a third palette that the homepage tests never saw: a 42px grid with a dot in
     every cell, two purple corner glows, and --ink:#121316. The tokens below are
     now the same values as css/main.css by design, not by copy-paste, and
     tests/palette.test.mjs reads both files so a second palette cannot reappear.

     Warm graphite surfaces, cool signal. */
  :root{
  /* Palette moved to css/tokens.css — that is the single source of truth now.
     Only the blog's own content colours remain here, and they are derived from
     the theme rather than being a second palette. The light set was warm paper
     (#EAE3D7 code block, #463B2E hover) which fought the pure-white page. */
  color-scheme:dark;
  --strong-text:#FFFFFF;
  --hover-text:#F5F3F0;
  --code-bg:#0C0A09;
}
html[data-theme="light"]{
  color-scheme:light;
  --strong-text:#000000;
  --hover-text:#1A1A1A;
  --code-bg:#F4F2EE;
}
  *{box-sizing:border-box; margin:0; padding:0;}
  html{scroll-behavior:smooth; color-scheme:dark; 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); font-family:var(--font-body); line-height:1.85;
    -webkit-font-smoothing:antialiased; transition:background-color .25s, color .25s;
    /* This used to be a 42px grid plus a dot in every cell plus two purple
       corner glows — three stacked patterns, the exact wallpaper that made the
       homepage look generated. The atmosphere now lives in css/tokens.css and is
       shared by every page, so the blog cannot drift from the homepage again. */
    font-optical-sizing:auto; text-rendering:optimizeLegibility;
  }
  html[lang="fa"] body, html[lang="ar"] body{font-synthesis:none;}
  html[lang="en"] body, html[lang="fr"] body{line-height:1.7;}
  h1,h2,h3,h4,.h-title,.post-title,.rel-title,.faq-heading{text-wrap:balance;}
  .prose p, .post-excerpt, .page-sub, .faq-answer p, .content p{text-wrap:pretty;}
  a{color:inherit; text-decoration:none;}
  html{-webkit-tap-highlight-color:transparent;}

  .skip-link{position:fixed; inset-inline-start:16px; top:16px; z-index:200; padding:8px 16px; border-radius: var(--radius); background-color:var(--panel); border:1px solid var(--line); color:var(--signal); font-size:13px; font-weight:700; opacity:0; pointer-events:none; transform:translateY(-8px); transition:opacity .2s, transform .2s;}
  .skip-link:focus{opacity:1; pointer-events:auto; transform:none;}
  /* Same contract as css/main.css: a real outline is the primary focus
     indicator because it survives forced-colors, where the box-shadow ring
     this used to rely on is stripped and `outline:none` left keyboard users
     with no indicator at all. summary is included because every FAQ on every
     article page is a <summary>, and it had no visible focus ring. */
  :focus-visible{outline:2px solid var(--signal); outline-offset:2px;}
  a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
  select:focus-visible, summary:focus-visible, [tabindex]:focus-visible{
    outline:2px solid var(--signal); outline-offset:2px;
    box-shadow:0 0 0 3px color-mix(in srgb, var(--signal) 40%, transparent); border-radius:4px;
  }
  .faq-item summary:focus-visible{outline-offset:-2px;}
  #page-blog{scroll-margin-top:96px;}

  /* The 20px above the bar is the mirror of the footer's 20px below its card.
     It was 14px here, which left the header's reserved band six pixels shorter
     than the footer's on this page and made the two look like different sizes
     even though the bar itself measured the same 54px on both. */
  header.topbar{position:sticky; top:calc(20px + env(safe-area-inset-top)); z-index:60; max-width:none; width:100%; margin-inline:auto; padding:0 var(--gutter); border-radius:999px;}
  /* The bar's side inset is the page gutter, the same number .template01-main
     above pads its content with, so the bar and the text under it share one
     edge at every width. It was a literal 12px against the container's 16px,
     which left the bar hanging 4px outside the text on both sides. */
  :root{--head-inset:var(--gutter);}
  .topbar-inner{display:flex; align-items:center; justify-content:space-between; height:var(--band-h); padding:0 12px; gap:10px; width:var(--band-w); max-width:100%; margin-inline:auto; border-radius:999px; background-color:var(--header-bg); backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur); border:1px solid var(--header-border); box-shadow:var(--header-shadow);}
  .brand{display:flex; align-items:center; gap:8px; font-weight:700; font-size:14px; flex:none; white-space:nowrap;}
  .brand .dot{width:8px; height:8px; border-radius:50%; background-color:var(--signal); box-shadow:0 0 0 3px var(--signal-dim);}
  .head-controls{display:flex; align-items:center; gap:8px; flex:none;}
  .flag-icon{width:18px; height:13px; border-radius:2px; display:block; box-shadow:0 0 0 1px var(--line);}
  .lang-wrap{position:relative;}
  /* Square footprint on purpose — see the identical rule in main.css. Auto
     width sized this to the flag, giving a 36x44 pill that no radius can
     round into a circle. */
  .lang-btn{display:flex; align-items:center; justify-content:center; gap:6px; width:44px; height:44px; padding:0; border:1px solid var(--line); border-radius:999px; background:transparent; color:var(--paper); font-size:12.5px; font-family:var(--font-body); cursor:pointer;}
  .lang-btn:hover{border-color:var(--signal);}
  .lang-menu{position:absolute; inset-inline-end:0; top:calc(100% + 8px); background-color:var(--panel); border:1px solid var(--line); border-radius:16px; padding:6px; min-width:52px; display:none; z-index:60; box-shadow:0 12px 30px rgba(0,0,0,.35);}
  .lang-menu.open{display:block; animation:menuPop .18s ease-out;}
  @keyframes menuPop{from{opacity:0; transform:translateY(-6px);} to{opacity:1; transform:none;}}
  .lang-opt{display:flex; align-items:center; justify-content:center; width:100%; min-height:44px; padding:8px; border-radius: var(--radius-sm); background:none; border:none; color:var(--paper); cursor:pointer; text-align:start;}
  .lang-opt .lo-flag{display:inline-flex; align-items:center;}
  .lang-opt:hover{background-color:var(--panel-3);}
  .lang-opt.active{color:var(--signal);}
  .icon-btn{
    display:flex; align-items:center; justify-content:center; width:44px; height:44px; border:1px solid var(--line);
    border-radius:999px; background:transparent; color:var(--paper); cursor:pointer; transition:border-color .15s, color .15s; flex:none;
  }
  .icon-btn:hover{border-color:var(--signal); color:var(--signal);}
  .icon-btn:active{transform:scale(.95);}
  .mob-btn{display:flex; align-items:center; justify-content:center; width:44px; height:44px; border:1px solid var(--line); border-radius:999px; background:transparent; color:var(--paper);}
  .mob-btn:hover{border-color:var(--signal); color:var(--signal);}
  .mob-btn:active{transform:scale(.95);}
  @media(min-width:1024px){.mob-btn{display:none;}}
  #mobile-nav{display:none; margin-top:8px; padding:10px 18px; border-radius: var(--radius); background-color:var(--header-bg); backdrop-filter:blur(24px) saturate(200%) brightness(1.08); border:1px solid var(--line); box-shadow:0 12px 30px -8px rgba(0,0,0,.35);}
  #mobile-nav.open{display:block; animation:menuPop .22s ease-out;}
  #mobile-nav .nb{display:flex; align-items:center; width:100%; min-height:44px; text-align:start; padding:10px 4px; font-size:14px; color:var(--muted); border:none; border-bottom:1px solid var(--line-soft); background:none; cursor:pointer; font-family:var(--font-body);}
  #mobile-nav .nb:hover{color:var(--paper);}
  #mobile-nav .nb.active{color:var(--signal);}

  /* Top padding is --head-fixed-h and bottom padding is the fixed footer's full
     band — the card plus the 20px inset under it, not the card alone. The
     document's last pixel meets the bottom of the viewport at full scroll, so
     the band covers that much of the viewport and the padding has to match it
     exactly.

     The top padding was 24px here, and that is the number the home page carried
     before it was fixed: 24px only clears the floating band's own top inset, so
     the first card began 4px under the header's lower edge instead of below it,
     on every width. The header is fixed, so this was visible on arrival and
     never scrolled away. Same token as main.css, so the two pages cannot drift
     apart again. */
  .template01-main{max-width:var(--page-w); margin:0 auto; padding:var(--head-fixed-h, 100px) var(--gutter) var(--foot-fixed-h, 88px); display:grid; gap:22px; grid-template-columns:1fr;}
  @media (min-width:1024px){.template01-main{grid-template-columns:300px minmax(0,1fr); align-items:start;}}
  .template01-content{min-width:0; display:grid; gap:22px; grid-template-columns:minmax(0,1fr); overflow-x:clip;}

  .template01-sidebar{position:relative; overflow:hidden; text-align:center; background:linear-gradient(165deg, var(--panel), var(--panel-2)); border:1px solid var(--line); border-radius: var(--radius-xl); box-shadow:var(--shadow-1); padding:30px 22px 26px;}
  /* .template01-sidebar::before accent hairline removed — see main.css for why
     the stripe came off every box at once. */
  /* .template01-sidebar::after decorative gradient square removed */
  .template01-sidebar .avatar-box{position:relative; z-index:1; width:128px; height:128px; margin:14px auto 18px; border-radius:50%; padding:4px; background-color:var(--grad); box-shadow:var(--shadow-2);}
  .template01-sidebar .avatar-box img{width:100%; height:100%; border-radius:50%; object-fit:cover; border:3px solid var(--panel); display:block;}
  /* See the note on the same rule in css/main.css: this removes the long-press
     and right-click save affordances for the profile photo. It is a speed bump,
     not protection — the limit that actually holds is the 320px served copy. */
  .template01-sidebar .avatar-box img{-webkit-user-drag:none; user-select:none; -webkit-touch-callout:none; pointer-events:none;}
  .template01-sidebar .name{margin:0; font-family:var(--font-display, 'Dana'); font-size:24px; font-weight:700; color:var(--paper);}
  .template01-sidebar .title{display:inline-block; margin:10px 0 0; padding:5px 18px; border-radius:999px; border:1px solid var(--line); background-color:var(--signal-dim); color:var(--tag-ink); font-size:13px; font-weight:600;}
  .info_more-btn{display:none;}
  .template01-sidebar .info-more{display:grid; gap:18px;}
  .template01-sidebar .separator{height:1px; background:linear-gradient(90deg, transparent, var(--line) 18%, var(--line) 82%, transparent);}
  .template01-sidebar .contacts-list{list-style:none; margin:0; padding:0; display:grid; gap:12px; text-align:start;}
  /* Not :last-child, because the info_more button follows it on mobile and is
     display:none on desktop. */
  .template01-sidebar .template01-socials{display:flex; align-items:center; justify-content:center; gap:6px; margin-top:20px;}
  .template01-sidebar .contact-item{display:flex; align-items:center; gap:11px; min-width:0;}
  /* Contact and social icons share one 30px rhythm. They were 34px and 44px,
     which made the social row wrap to 4+3 inside the 300px column and turned
     the box into the tallest thing on the page. 7 x 30 + 6 x 6 = 246px, which
     fits the 256px inner width on one line with room to spare. 30px is still
     above the 24px WCAG 2.5.8 minimum for a target. */
  .template01-sidebar .contact-item .icon-c{flex:none; width:30px; height:30px; border-radius: var(--radius-sm); border:1px solid var(--line); display:flex; align-items:center; justify-content:center; color:var(--signal); background-color:var(--panel-2);}
  .template01-sidebar .contact-item .icon-c svg{width:15px; height:15px;}
  .template01-sidebar .template01-link{font-size:13px; color:var(--paper); overflow-wrap:anywhere; text-decoration:none; min-width:0; line-height:1.6; transition:color .2s;}
  .template01-sidebar .template01-link:hover{color:var(--signal);}
  /* 30px to match .icon-c, 7 x 30 + 6 x 6 = 246px in a 256px column. Colours
     come from the [data-platform] rules in tokens.css. */
  .template01-sidebar .template01-socials a{
    width:30px; height:30px; flex:none;
    display:flex; align-items:center; justify-content:center;
    border-radius: var(--radius-sm); border:1px solid var(--line);
    color:var(--brand-ink);
    background-color:var(--panel-2);
    transition:color .15s, background-color .15s, border-color .15s;
  }
  .template01-sidebar .template01-socials a svg{width:15px; height:15px;}
  .template01-sidebar .template01-socials a:hover,
  .template01-sidebar .template01-socials a:focus-visible,
  .template01-sidebar .template01-socials a:active{
    color:var(--brand-on);
    background-color:var(--brand);
    border-color:var(--brand);
  }
  /* Same story as main.css: the sidebar's copy of the seven social links is
     gone, and the sticky offset is now the one the floating header actually
     needs — 20px + a --band-h bar puts its lower edge at 80px, so 94px clears
     it with 14px of air instead of the 152px this used to carry.

     max-height and overflow-y are for the same reason as on index.html, and the
     card is a pixel taller here: without them it needs a 674px-tall window to
     fit between the offset and the fixed footer, and on a 1366x620 laptop the
     social links ended up painted under the footer and not clickable.

     The 660px of height in the query is load-bearing, for the reason given in
     main.css: a pinned card is only pinned on a window that can show it whole. */
  @media (min-width:1024px) and (min-height:660px){
    .template01-sidebar{
      position:sticky; top:94px;
      /* The gap between the pin and the footer. See main.css for why this is
         not bounded by the resting offset, and why the 660px in the query is
         what keeps the card whole rather than a smaller cap. */
      max-height:calc(100dvh - 94px - var(--foot-fixed-h, 88px));
      overflow-y:auto; overscroll-behavior:contain;
    }
  }
  @media (max-width:1023px){
    .template01-sidebar .info-more{display:none;}
    .template01-sidebar.active .info-more{display:grid;}
    .info_more-btn{display:flex; align-items:center; justify-content:center; min-height:44px; margin:16px auto 0; border:1px solid var(--line); background-color:var(--panel-2); color:var(--signal); font-size:13px; font-weight:700; padding:10px 20px; border-radius:999px; cursor:pointer; transition:background .2s, border-color .2s;}
    .info_more-btn:hover{background-color:var(--signal-dim); border-color:var(--tag-ink); color:var(--tag-ink);}
  }

  .template01-navbar{display:flex; align-items:center; gap:2px; flex:1 1 auto; justify-content:center; overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none; padding:0 4px; white-space:nowrap;}
  .template01-navbar::-webkit-scrollbar{display:none;}
  .template01-navbar .nb{flex:none; display:inline-flex; align-items:center; gap:4px; padding:8px 12px; border-radius:999px; border:1px solid transparent; background:transparent; color:var(--muted); font-size:13.5px; font-weight:600; font-family:var(--font-body); cursor:pointer; text-decoration:none; white-space:nowrap; transition:color .18s, background .18s, box-shadow .18s, transform .12s;}
  .template01-navbar .nb:hover{color:var(--paper);}
  .template01-navbar .nb:active{transform:scale(.95);}
  .template01-navbar .nb.active{color:var(--on-accent); background-color:var(--grad); box-shadow:0 8px 18px -8px color-mix(in srgb, var(--signal) 23%, transparent);}
  @media (max-width:1023px){ .template01-navbar{display:none;} }

  .template01-pages{display:block;}
  .template01-page{display:none;}
  .template01-page.active{display:block; animation:template01FadeUp .38s cubic-bezier(.2,.7,.3,1) both;}
  @keyframes template01FadeUp{from{opacity:0; transform:translateY(12px);} to{opacity:1; transform:none;}}

  .blog-head{margin-bottom:28px;}
  .eyebrow{font-family:var(--font-body); font-size:12.5px; color:var(--signal); letter-spacing:.04em;}
  .h-title{position:relative; font-weight:700; letter-spacing:-.012em; font-size:clamp(1.625rem, 0.5rem + 2.6vw, 2.125rem);}
  :lang(fa) .h-title,:lang(ar) .h-title{letter-spacing:0;}
  /* .h-title::after accent dash removed — see main.css for why. */
  .page-sub{color:var(--muted); font-size:14px; margin-top:10px; margin-bottom:34px;}

  .post-grid{display:grid; gap:18px;}
  @media(min-width:720px){.post-grid{grid-template-columns:1fr 1fr;}}
  /* No content-visibility here. It was `content-visibility:auto` with
     `contain-intrinsic-size:auto 190px`, so every card that had not yet been
     scrolled past was laid out at the 190px hint and then re-laid-out at its
     real height the moment it entered the viewport. Measured in the browser at
     320px, a real card is 209px tall, so each of the twelve shifted 19px on
     the way down the page — a cumulative layout shift on the one page whose
     whole job is a scrolling list of cards, and the shift landed on the row
     below, moving the thing the visitor was reaching for.

     The hint could not have been right either way: the height depends on the
     title wrapping to one line or two and the excerpt to three, in five
     languages, so no single number is correct for all twenty-six pages. The
     saving was 12 text cards, which is not a measurable amount of work. */
  .post-card{position:relative; background:linear-gradient(165deg, var(--panel), var(--panel-2)); border:1px solid var(--line); border-radius: var(--radius-xl); overflow:hidden; box-shadow:var(--shadow-1); transition:border-color .25s, transform .25s; display:block;}
  /* .post-card::before accent hairline removed — see main.css for why. The card's
     own surface, border and shadow are what make it a box. */
  .post-card:hover{border-color:var(--signal); transform:translateY(-4px);}
  .post-body{padding:18px 20px;}
  .post-date{font-family:var(--font-body); font-size:11px; color:var(--muted-2); margin-bottom:8px;}
  .post-title{font-size:16.5px; font-weight:700; margin:0 0 8px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; line-clamp:2;}
  .post-excerpt{color:var(--muted); font-size:14px; line-height:1.8; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; line-clamp:3;}
  .empty-state{color:var(--muted); text-align:center; padding:60px 20px; font-size:14px; background:linear-gradient(165deg, var(--panel), var(--panel-2)); border:1px solid var(--line); border-radius: var(--radius); box-shadow:var(--shadow-1);}

  .single-post{position:relative; background:linear-gradient(165deg, var(--panel), var(--panel-2)); border:1px solid var(--line); border-radius: var(--radius-xl); box-shadow:var(--shadow-1); padding:26px 20px;}
  @media(min-width:768px){.single-post{padding:36px 34px;}}
  /* .single-post::before accent hairline removed — it also carried a different
     corner radius from the card it sat on, 12px/22px against the card's 12px, so
     the stripe did not follow the box it was drawn on. */
  .single-post .post-date{font-size:12.5px; margin-bottom:10px;}
  .single-post h1{font-size:clamp(1.5rem, 0.5rem + 2.4vw, 2.25rem); font-weight:700; letter-spacing:-.012em; margin-bottom:22px; line-height:1.35;}
  :lang(fa) .single-post h1,:lang(ar) .single-post h1{letter-spacing:0;}
  .single-post .content p{color:var(--muted); font-size:16px; line-height:2; margin-bottom:20px;}
  .back-to-list{display:inline-flex; align-items:center; justify-content:center; gap:6px; min-height:44px; margin-top:32px; font-size:13.5px; color:var(--on-accent); border:1px solid transparent; border-radius: var(--radius); padding:9px 16px; background-color:var(--grad); box-shadow:0 10px 24px -10px color-mix(in srgb, var(--signal) 23%, transparent);}
  .back-to-list:hover{filter:brightness(1.08);}

  /* Footer styles moved to css/footer.css — one card for all three page
     types. What is left here is only the width, because the article pages
     set their own. */

  /* Weight, not stroke, carries the heading hierarchy here.

     This rule used to add `-webkit-text-stroke:.5px` on top of `font-weight:900`,
     which made the blog headings look heavy rather than bold. Two reasons the
     stroke was doing all the work and the weight nothing:

     Dana is a single static face announced as `font-weight:100 900`, so the
     browser serves the same file for every weight and there is no real 900 to
     render. Measured in-browser, the rendered advance of a sample string is
     identical at 400 and at 900 — the weight axis does not exist here.

     What did change with weight was anti-aliasing, not stem width, so the
     stroke was doing exactly the job the weight could not: adding pixels to
     the outside of the glyph. On a 16.5px .post-title half a pixel of outline
     on each side is a large share of the stem, and the text reads as smeared
     rather than emphasised.

     700 is what the file can actually distinguish, and it is a real step up
     from the 400 body text. The one heading that genuinely needs presence at
     this size is the page title, which carries 34px. */
  .page-title,.post-title,.single-post h1,h2,h3{font-weight:700; letter-spacing:-.012em;}

  /* Scoped to .js for the same reason as css/main.css: with JS off nothing can
     add .in, so an unscoped opacity:0 would hide the page content entirely. */
  .js [data-reveal]{opacity:0; transform:translateY(14px); transition:opacity .6s ease, transform .6s ease;}
  .js [data-reveal].in{opacity:1; transform:none;}

  @media (prefers-reduced-motion: reduce){
    .js [data-reveal]{opacity:1; transform:none; transition:none;}
    .template01-page.active{animation:none;}
    .post-card{transition:none;}
    .post-card:hover{transform:none;}
    .template01-navbar .nb:active,.icon-btn:active,.mob-btn:active,.lang-btn:active{transform:none;}
    html{scroll-behavior:auto;}
    *{animation:none !important;}
  }

  /* Desktop is an ordinary scrolling document here too, for the same reasons as
     index.html: the 100dvh/overflow:hidden shell pinned the footer to the
     viewport, hid the page scrollbar inside the layout, and broke in-page
     anchors because a browser never scrolls an inner container. See the
     matching block in main.css for the full account. */
  @media (min-width:1024px){
    .template01-main{grid-template-columns:300px minmax(0,1fr); align-items:start;}
  }

  #single-view{display:none;}
  a,button,input,textarea,summary,label{touch-action:manipulation;}
  /* The unreachable data-header variants are removed here for the same reason as
     in main.css: no page in the project sets the attribute, so all nine rules
     were dead, and the ones that squared the corners or switched the film off
     read as a supported alternative to the glass pill when they were not. See
     the note in main.css for the full list. */

  @media(max-width:400px){
    .topbar-inner{gap:7px; padding:0 12px;}
  }
  @media(max-width:360px){
    .topbar-inner{gap:7px; padding:0 12px;}
    .head-controls{gap:6px;}
    .icon-btn,.mob-btn,.lang-btn{width:36px; height:36px; padding:0;}
  }

  .single-post .content h2{font-size:clamp(19px, 2.2vw + 4px, 24px); font-weight:700; margin:34px 0 14px; line-height:1.45;}
  .single-post .content h3{font-size:17.5px; font-weight:700; margin:26px 0 12px; line-height:1.45;}
  .single-post .content h2, .single-post .content h3, .single-post .content h4{scroll-margin-top:116px;}
  .single-post .content ul, .single-post .content ol{margin:0 0 20px; padding-inline-start:26px;}
  .single-post .content li{margin-bottom:8px; line-height:1.95;}
  .single-post .content strong{color:var(--strong-text); font-weight:800;}
  .single-post .content a{color:var(--signal); text-decoration:underline; text-decoration-color:var(--signal-dim); text-underline-offset:5px; transition:color .15s;}
  .single-post .content a:hover{color:var(--hover-text);}
  .single-post .content pre{background-color:var(--code-bg); border:1px solid var(--line); border-radius: var(--radius-sm); padding:16px 18px; margin:0 0 22px; overflow-x:auto; direction:ltr; text-align:left; font-family:var(--mono); font-size:13.5px; line-height:1.75; box-shadow:var(--shadow-1);}
  .single-post .content code{font-family:var(--mono);}
  .single-post .content p code, .single-post .content li code{background-color:var(--panel-2); border:1px solid var(--line); border-radius:6px; padding:1px 6px; font-size:.9em; direction:ltr;}

  .post-meta{display:flex; flex-wrap:wrap; align-items:center; gap:10px 16px; margin-bottom:22px;}
  .post-meta .post-date{margin:0;}
  .post-meta .reading-time{color:var(--signal); font-weight:600;}
  .post-meta .dot{color:var(--muted-2);}
  .post-tags{display:flex; flex-wrap:wrap; gap:8px;}
  .post-tags .tag{font-size:11px; color:var(--tag-ink, var(--signal)); background-color:var(--signal-dim); border:1px solid var(--line); padding:4px 12px; border-radius:999px;}

  .faq-heading{font-size:clamp(19px, 2.2vw + 4px, 24px); font-weight:700; margin:36px 0 16px;}
  .faq-item{border:1px solid var(--line); border-radius: var(--radius-sm); background-color:var(--panel-2); 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-posts{margin-top:40px; padding-top:26px; border-top:1px solid var(--line);}
  .related-posts h2{font-size:18px; margin-bottom:18px;}
  .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(--panel-2)); border:1px solid var(--line); border-radius: var(--radius-sm); overflow:hidden; padding:14px 0; 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; 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:2000;
  background-color:var(--grad); transform-origin:left center; transform:scaleX(0);
  pointer-events:none; will-change:transform; border-radius:0 3px 3px 0;
}
[dir="rtl"] .progress-bar{ transform-origin:right center; border-radius:3px 0 0 3px; }

/* ======================= blog filters: search + chips ======================= */
.blog-filters{display:flex; flex-direction:column; gap:12px; margin-bottom:26px;}
.search-bar{
  width:100%; background-color:var(--panel-2); border:1px solid var(--line); border-radius: var(--radius);
  color:var(--paper); padding:11px 16px; font-family:var(--font-body); font-size:14.5px;
  transition:border-color .2s, box-shadow .2s;
}
/* A transparent outline buys nothing but costs the focus indicator: it is
   invisible normally and dropped outright in forced-colors. Use a real one. */
.search-bar:focus{border-color:var(--signal); box-shadow:0 0 0 3px var(--signal); outline:2px solid var(--signal); outline-offset:1px;}
.search-bar:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb, var(--signal) 40%, transparent); outline:2px solid var(--signal);}
.chips{display:flex; flex-wrap:wrap; gap:8px;}
.chip{
  background-color:var(--panel-2); color:var(--muted); border:1px solid var(--line); border-radius:999px;
  padding:7px 15px; font-size:12.5px; font-weight:600; font-family:var(--font-body); cursor:pointer; min-height:40px; display:inline-flex; align-items:center;
  transition:background-color .2s, color .2s, border-color .2s, transform .12s;
}
.chip:hover{color:var(--paper); border-color:var(--signal);}
  .chip.active{background-color:var(--signal); color:var(--on-accent); border-color:var(--signal);}
.chip:active{transform:scale(.96);}
.blog-no-results{padding:26px; text-align:center; color:var(--muted-2); border:1px dashed var(--line); border-radius: var(--radius-sm); font-size:14px;}
.blog-results-status{position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; overflow:hidden;}

/* ======================= table of contents ======================= */
.toc{
  margin:6px 0 30px; padding:18px 20px; background-color:var(--panel-2);
  border:1px solid var(--line); border-radius: var(--radius-sm);
}
.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 buttons ======================= */
/* .post-nav and .post-share are the same components as the ones in
   blog-post.css, and they were two numbers apart: 34px here against 40px there
   for the nav, 6/8px against 8/6px for the share row. A card in the listing and
   the same card on the article it links to are one component, so they now both
   read the scale in tokens.css. */
.post-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: var(--radius-sm);
  background-color:var(--panel-2); border:1px solid var(--line); color:var(--paper); cursor:pointer;
  font-family:var(--font-body); font-size:12.5px; font-weight:600;
  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);}

/* The share buttons carry a text label, so they do not take the solid brand
   fill the icon-only links use — a coloured pill behind "Telegram" reads as a
   mis-styled button, not a branded one. They take the brand hue on the border
   and the glyph instead, which is enough to tell the networks apart while the
   label keeps the button's meaning. */
.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:48%; display:flex; flex-direction:column; gap:5px;
  background-color:var(--panel-2); border:1px solid var(--line); border-radius: var(--radius-sm); padding:14px 16px;
  transition:border-color .2s, background-color .2s;
}
.post-nav-link:hover{border-color:var(--signal); background-color:var(--panel);}
.nav-label{font-size:11px; font-weight:700; color:var(--signal);}
.nav-title{font-size:13.5px; font-weight:700; line-height:1.6; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;}
.nav-slot{flex:1;}

/* ======================= empty states / misc ======================= */
.empty-state{padding:22px 18px; color:var(--muted-2); border:1px dashed var(--line); border-radius: var(--radius-sm); font-size:14px; line-height:1.7; text-align:center;}

/* Shown in place of an article body when content-posts.json could not be
   loaded. Sits in the reading column, so it inherits the article's width and
   does not shift the layout the way a full-bleed banner would. */
.content-missing{
  display:flex; align-items:center; gap:12px;
  padding:22px 20px; margin:0 0 8px;
  color:var(--muted-2);
  background:var(--panel);
  border:1px dashed var(--line);
  border-radius: var(--radius);
  font-size:15px; line-height:1.8; text-align:start;
}
.content-missing::before{
  content:"";
  flex:none; width:20px; height:20px; border-radius:50%;
  border:2px solid var(--line);
  border-top-color:var(--signal);
  animation:contentMissingSpin 1s linear infinite;
}
/* The spinner is decorative and must not be the only signal, so it stops for
   anyone who asked for reduced motion; the text still carries the meaning. */
@media (prefers-reduced-motion: reduce){
  .content-missing::before{animation:none;}
}
@keyframes contentMissingSpin{to{transform:rotate(360deg);}}

/* ======================= hero avatar skeleton ======================= */
.avatar-box{overflow:hidden;}
.avatar-box img{position:relative; z-index:1; opacity:1;}
.avatar-box::after{
  content:""; position:absolute; top:0; bottom:0; left:0; width:160%; z-index:0;
  background:linear-gradient(100deg, transparent 35%, color-mix(in srgb, var(--paper) 16%, transparent) 50%, transparent 65%);
  transform:translateX(-125%); opacity:0; transition:opacity .3s ease;
  animation:shimmer-sweep 1.5s linear infinite; pointer-events:none;
}
.js .avatar-box:has(img:not(.loaded))::after{opacity:1;}
/* Photo visible from first paint; the sweep sits behind it (see css/main.css). */
@keyframes shimmer-sweep{0%{transform:translateX(-125%);} 100%{transform:translateX(125%);}}

/* ======================= search bar ======================= */
.search-bar{font-size:16px;}

/* ======================= noscript post list ======================= */
.noscript-posts{
  padding:22px 20px; border:1px dashed var(--line); border-radius: var(--radius);
  background-color:var(--panel); color:var(--muted); font-size:14px; line-height:2;
}
.noscript-posts .ns-label{display:block; font-weight:800; color:var(--paper); margin-bottom:6px;}
.noscript-posts ul{list-style:none; margin:0; padding:0; display:grid; gap:2px;}
.noscript-posts a{color:var(--signal); text-decoration:none;}
.noscript-posts a:hover{text-decoration:underline;}

/* ======================= micro-interactions ======================= */
.btn,.mob-btn,.lang-btn,.nb,.icon-btn,.info_more-btn,.post-card{transition:transform .12s ease, background-color .25s ease, border-color .25s ease, color .25s ease, box-shadow .25s ease;}
.btn:active,.mob-btn:active,.lang-btn:active,.icon-btn:active,.info_more-btn:active{transform:scale(.96);}
body,.topbar-inner,.template01-sidebar .info-content,.search-bar,pre,.faq-item,.rel-card{transition:background-color .3s ease, border-color .3s ease, color .3s ease;}

@media (prefers-reduced-motion: reduce){
  .avatar-box::after{animation:none; opacity:0;}
  .chip,.share-btn,.post-nav-link,.rel-card,.toc-link{transition:none;}
  *{transition:none !important;}
}