/* =========================================================
   Gem Store — articles (the galaxy)
   The ARCHIVE, plus every primitive the reading page borrows:
   the frosted pane, the art sky, the planets, the tag.
   The reading page itself lives in article.css.

   Rides on style.css: same tokens, same nav, same starfield.
   Deliberately self-contained beyond that. In WordPress the
   blog is a different set of template files from the shop, so
   nothing here may depend on product.css — otherwise archive.php
   would have to load the single-product stylesheet to look right.
   The frosted surface is repeated (as `.pane`) for exactly that
   reason; it is thirty lines, and the alternative is a dependency
   that outlives this prototype.
   ========================================================= */

/* ---------------- tokens ---------------- */
:root, :root[data-theme="light"]{
  --pane-bg:      rgba(255,255,255,.58);
  --pane-solid:   rgba(255,255,255,.94);
  --pane-line:    rgba(255,255,255,.72);
  --pane-shadow:  var(--e-2),
                  0 0 0 1px rgba(11,11,16,.055),
                  inset 0 1px 0 rgba(255,255,255,.9);
  --pane-sheen:   .55;

  /* The galaxy is drawn in the SAME ink as the starfield, so on the light
     theme it is a black-dot engraving on white rather than a glowing disc.
     That reads as an astronomy plate, which is the right register here — but
     it needs far less opacity than the dark theme or it turns into dirt. */
  --galaxy-op:    .46;
  --galaxy-core:  .30;

  /* Art panels stay pale in light mode, like every other art panel on the
     site (.prod__art, .card__art). A "window into space" that goes black on
     a white page reads as a hole punched in the layout. */
  --neb-floor:    var(--surface-2);
  --neb-op:       .95;
  /* The planet's night side. Dark in both themes — it is a shadow, not a
     surface, and a pale one makes the sphere read as a flat disc. */
  --limb-core:    #1A1830;
  --limb-shade:   rgba(11,11,16,.30);

  --toc-idle:     var(--line);
}
:root[data-theme="dark"]{
  --pane-bg:      rgba(27,26,41,.48);
  --pane-solid:   rgba(20,19,31,.96);
  --pane-line:    rgba(255,255,255,.10);
  --pane-shadow:  var(--e-2),
                  0 0 0 1px rgba(255,255,255,.045),
                  inset 0 1px 0 rgba(255,255,255,.085);
  --pane-sheen:   .10;

  --galaxy-op:    .95;
  --galaxy-core:  .72;

  --neb-floor:    #0C0B16;
  --neb-op:       1;
  --limb-core:    #08070E;
  --limb-shade:   rgba(0,0,0,.62);

  --toc-idle:     #3A3752;
}

/* The four-point star, as a mask. Same path the starfield and the search
   asterism use, so a heading bullet and a background star are the same shape —
   which is what makes the motif read as one system instead of three ideas. */
:root{
  --star-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1.4 -1.4 2.8 2.8'%3E%3Cpath d='M0 -1.15C.18 -.36 .36 -.18 1.15 0 .36 .18 .18 .36 0 1.15 -.18 .36 -.36 .18 -1.15 0 -.36 -.18 -.18 -.36 0 -1.15Z'/%3E%3C/svg%3E");
}

/* ---------------- the frosted surface ---------------- */
.pane{
  position:relative;
  background:var(--pane-bg);
  backdrop-filter:blur(var(--pane-blur, 20px)) saturate(160%);
  -webkit-backdrop-filter:blur(var(--pane-blur, 20px)) saturate(160%);
  border:1px solid var(--pane-line);
  border-radius:var(--r-lg);
  box-shadow:var(--pane-shadow);
}
/* Rim light from the top-start corner. Without it a translucent white panel on
   a white page has no edge at all. */
.pane::before{
  content:''; position:absolute; inset:0; z-index:0;
  border-radius:inherit; pointer-events:none;
  background:linear-gradient(150deg,
    rgba(255,255,255,var(--pane-sheen)) 0%,
    rgba(255,255,255,calc(var(--pane-sheen) * .22)) 28%,
    transparent 55%);
}
.pane > *{ position:relative; z-index:1; }

/* Nine article cards blurring the backdrop nine times costs the entire frame
   budget and changes almost nothing on screen — what is behind them is the
   starfield, which is already soft. Real backdrop-filter is spent on the two
   large panels (the featured card and the article body) only. */
.pane--lite{
  backdrop-filter:none; -webkit-backdrop-filter:none;
  background:color-mix(in srgb, var(--pane-bg) 70%, var(--surface));
}
.pane--lite::before{ opacity:.5; }

@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .pane{ background:var(--pane-solid); }
}

/* ---------------- page shell ----------------
   Its own stacking context, so nothing inside can out-paint the sticky nav
   (z-index 1 in the root context). */
.blog{
  position:relative; z-index:0;
  max-width:1240px; margin-inline:auto;
  padding:var(--s-5) var(--s-5) var(--s-9);
  --pane-blur:20px;
}
.blog > *{ position:relative; z-index:1; }

/* ═════════════════ 1. the galaxy ═════════════════
   A two-armed logarithmic spiral, drawn point by point in blog.js and turned
   in three shells at three different speeds. Real discs do not rotate rigidly:
   the inside laps the outside. One group turning as a unit immediately reads
   as a sticker spinning, which is the whole thing this is trying not to be. */
.galaxy{
  position:relative;
  display:grid; align-items:center;
  min-block-size:clamp(360px, 46vw, 520px);
  margin-block-end:var(--s-8);
  /* The arms are wider than the column on purpose. `clip` (not `hidden`)
     creates no scroll container and leaves the Y axis alone. */
  overflow-x:clip;
}
.galaxy__sky{
  position:absolute; inset-block-start:50%;
  /* Physical `left`: this is a visual anchor, not something that should follow
     reading direction. RTL puts the copy on the right, so the core sits left. */
  left:-16%;
  inline-size:min(1120px, 118%); aspect-ratio:1200 / 640;
  transform:translateY(-50%);
  opacity:var(--galaxy-op);
  pointer-events:none; overflow:visible;
  /* Dissolve the rim into the page starfield. A galaxy that ends at a straight
     edge reads as an image sitting on the page rather than as depth behind it.
     70% keeps the two inner shells at full strength and spends the fade on the
     outer one, which is the faint halo anyway. */
  -webkit-mask-image:radial-gradient(closest-side, #000 70%, transparent 100%);
          mask-image:radial-gradient(closest-side, #000 70%, transparent 100%);
}
.galaxy__shell{
  transform-box:fill-box; transform-origin:50% 50%;
  animation:g-turn var(--spin, 200s) linear infinite;
  will-change:transform;
}
.galaxy__shell--rev{ animation-direction:reverse; }
@keyframes g-turn{ to{ transform:rotate(360deg); } }

/* Colour is set by class, not by a `fill="var(…)"` attribute: presentation
   attributes are not CSS declarations and do not resolve var(). */
.g-dot{ fill:var(--star); }
.g-dot--v{ fill:var(--violet); }
.g-dot--c{ fill:var(--cyan); }
.g-dot--a{ fill:var(--amber); }
.g-dot--p{ fill:var(--pink); }
.galaxy__core{ opacity:var(--galaxy-core); }
.galaxy__glow{ opacity:calc(var(--galaxy-core) * .55); }

.galaxy__copy{ position:relative; z-index:1; max-inline-size:56ch; padding-block:var(--s-6); }
.galaxy__copy h1{ font-size:var(--t-2xl); font-weight:900; margin-block:var(--s-3) var(--s-4); }
.galaxy__lede{ margin:0; color:var(--text-muted); max-inline-size:50ch; }
.galaxy__stats{
  list-style:none; display:flex; flex-wrap:wrap; gap:var(--s-6);
  margin:var(--s-6) 0 0; padding:0;
}
.galaxy__stats li{ display:flex; flex-direction:column; }
.galaxy__stats strong{ font-size:var(--t-lg); font-weight:900; }
.galaxy__stats span{ font-size:var(--t-sm); color:var(--text-muted); }

/* ═════════════════ 2. shared art panel ═════════════════
   The nebula wash + the planet limb. Used by the featured card, the article
   cards and the single-article hero, so a card and the page it opens are
   visibly the same object. --accent is set per item. */
.sky{
  position:relative; overflow:hidden;
  background:
    radial-gradient(120% 92% at 76% 6%,  color-mix(in oklch, var(--accent) 34%, transparent), transparent 62%),
    radial-gradient(94% 74% at 10% 96%,  color-mix(in oklch, var(--cyan) 24%, transparent),   transparent 66%),
    var(--neb-floor);
}
/* Bleeds off the corner. A planet fully inside the frame reads as a ball on a
   table; cropped, it reads as a body you are flying past. */
.sky__orb{
  position:absolute; inline-size:var(--orb, 62%); aspect-ratio:1; border-radius:50%;
  inset-block-end:var(--orb-y, -36%); inset-inline-start:var(--orb-x, -8%);
  background:
    radial-gradient(circle at 34% 26%,
      color-mix(in oklch, var(--accent) 62%, white) 0%,
      var(--accent) 34%,
      color-mix(in oklch, var(--accent) 46%, var(--limb-core)) 66%,
      var(--limb-core) 100%);
  /* Two shadows doing different jobs: the inset is the terminator that curves
     the sphere, the outer one is the light it throws on the dust around it. */
  box-shadow:
    inset -12px -14px 34px var(--limb-shade),
    0 0 60px color-mix(in oklch, var(--accent) 28%, transparent);
}
/* The category glyph. Top-start corner: the tag holds the top-end one and the
   planet owns the bottom-start, so this is the only quiet corner left. */
.sky__glyph{
  position:absolute; inset-block-start:var(--s-4); inset-inline-start:var(--s-4); z-index:2;
  inline-size:26px; block-size:26px; color:var(--accent);
  filter:drop-shadow(0 0 8px color-mix(in oklch, var(--accent) 45%, transparent));
}

/* تصویر شاخصِ واقعی — وقتی نویسنده‌ای آپلودش کند روی سحابی و سیاره‌ی
   تولیدشده می‌نشیند؛ اگر نبود همان دو تا جای خالی را پر می‌کنند. z-index
   بین آن‌ها (0) و گلیف/برچسب/دنباله‌دار (2) قرار می‌گیرد تا هیچ‌کدام از
   آن‌ها زیر عکس گم نشوند. */
.post__thumb{
  position:absolute; inset:0; inline-size:100%; block-size:100%;
  object-fit:cover; z-index:1;
}

/* A few bright foreground stars over the wash, positioned from JS. */
.sky__star{
  position:absolute; display:block; background:var(--star);
  -webkit-mask:var(--star-mask) center/contain no-repeat;
          mask:var(--star-mask) center/contain no-repeat;
  opacity:var(--o, .3);
}
:root[data-theme="dark"] .sky__star{ background:#FFFFFF; }

/* The comet. Parked outside the frame and flown across on hover — a light
   sweep is the cheapest way to say "this is a link" without moving the card
   contents, which on a text card is what makes hover feel unstable. */
.sky__comet{
  position:absolute; inset-block-start:-40%; left:-45%;
  inline-size:55%; block-size:190%;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.42) 46%, rgba(255,255,255,.72) 52%, transparent);
  transform:rotate(19deg) translateX(-160%);
  transition:transform .85s var(--ease-out);
  pointer-events:none; mix-blend-mode:soft-light;
}
:root[data-theme="dark"] .sky__comet{ mix-blend-mode:screen; }

/* ═════════════════ 3. featured — the ringed world ═════════════════ */
.nova{
  display:grid; gap:0; overflow:hidden;
  margin-block-end:var(--s-8);
  --accent:var(--indigo);
}
@media (min-width:900px){
  .nova{ grid-template-columns:minmax(0,1.06fr) minmax(0,1fr); align-items:stretch; }
}
/* No corner radius of its own — .nova clips it. */
.nova__art{ position:relative; display:block; min-block-size:280px; aspect-ratio:16 / 11; }
@media (min-width:900px){ .nova__art{ aspect-ratio:auto; } }

/* The ring is a plain circle turned 72° about X. No `perspective` on purpose:
   an orthographic rotateX collapses to an exact ellipse (Y × cos 72° = .309),
   and that exactness is what lets the moon below trace the same path with a
   simple scaleY instead of a motion path in absolute pixels. */
.nova__system{
  position:absolute; inset-block-end:-16%; inset-inline-start:-6%;
  inline-size:min(78%, 380px); aspect-ratio:1;
  animation:float 13s ease-in-out infinite;
}
@keyframes float{ 50%{ transform:translateY(-14px); } }
.nova__planet{
  position:absolute; inset:19%; z-index:1; border-radius:50%;
  background:
    radial-gradient(circle at 34% 26%,
      color-mix(in oklch, var(--accent) 62%, white) 0%,
      var(--accent) 34%,
      color-mix(in oklch, var(--accent) 44%, var(--limb-core)) 66%,
      var(--limb-core) 100%);
  box-shadow:
    inset -14px -16px 40px var(--limb-shade),
    0 0 70px color-mix(in oklch, var(--accent) 32%, transparent);
}
.nova__ring{
  position:absolute; inset:0; border-radius:50%;
  border:2px solid color-mix(in oklch, var(--accent) 52%, transparent);
  box-shadow:0 0 0 6px color-mix(in oklch, var(--accent) 12%, transparent);
  transform:rotate(-18deg) rotateX(72deg);
}
/* The same ring again, clipped to its near half and lifted over the planet.
   clip-path is applied in the element's own box BEFORE the transform, and
   rotateX(72deg) maps the local bottom half onto the near side of the ring —
   so this is the half that must pass in front. */
.nova__ring--fore{ z-index:2; clip-path:inset(50% 0 0 0); }

/* The moon. `.nova__orbit` squashes a circular path into the ring's ellipse;
   `.nova__spin` walks the moon around it; the moon counter-rotates on the same
   clock so the squash never reaches its own shape (the two rotations cancel in
   the composite, leaving scaleY(.309) × scaleY(3.236) = identity). */
.nova__orbit{
  position:absolute; inset:0; z-index:0;
  transform:rotate(-18deg) scaleY(.309);
  animation:moon-depth var(--moon-dur, 21s) linear infinite;
}
.nova__spin{
  position:absolute; inset:0;
  animation:g-turn var(--moon-dur, 21s) linear infinite;
}
.nova__moon{
  position:absolute; inset-block-start:-8px; left:calc(50% - 8px);
  inline-size:16px; block-size:16px; border-radius:50%;
  background:radial-gradient(circle at 34% 30%, #FFF, color-mix(in oklch, var(--accent) 30%, #9C9AB4) 62%, var(--limb-core));
  transform:scaleY(3.236);
  animation:g-turn var(--moon-dur, 21s) linear infinite reverse;
}
/* Behind the planet on the far quarter-turns, in front across the near half.
   Stepped rather than eased: z-index has no meaningful in-between value, and
   the swap lands at the sides of the disc where nothing overlaps anyway. */
@keyframes moon-depth{
  0%,   24.9%  { z-index:0; }
  25%,  74.9%  { z-index:3; }
  75%,  100%   { z-index:0; }
}

.nova__body{ display:flex; flex-direction:column; gap:var(--s-3); padding:clamp(var(--s-5), 2.6vw, var(--s-7)); }
.nova__title{ font-size:var(--t-xl); font-weight:900; }
.nova__title a{ transition:color .18s var(--ease-out); }
.nova__title a:hover{ color:var(--accent); }
.nova__excerpt{ margin:0; color:var(--text-muted); max-inline-size:52ch; }
.nova__cta{ align-self:flex-start; margin-block-start:var(--s-3); }

/* ═════════════════ 4. the tag on every card ═════════════════ */
.tag{
  display:inline-flex; align-items:center; gap:6px;
  padding:3px 11px; border-radius:var(--r-full);
  font-size:.75rem; font-weight:700; color:var(--accent);
  background:color-mix(in oklch, var(--accent) 13%, var(--surface));
  border:1px solid color-mix(in oklch, var(--accent) 28%, transparent);
}
.tag::before{
  content:''; inline-size:8px; block-size:8px; flex:none; background:currentColor;
  -webkit-mask:var(--star-mask) center/contain no-repeat;
          mask:var(--star-mask) center/contain no-repeat;
}
.tag--float{ position:absolute; inset-block-start:var(--s-3); inset-inline-end:var(--s-3); z-index:2; backdrop-filter:blur(6px); }

/* ═════════════════ 5. the grid ═════════════════ */
.posts{ display:grid; gap:var(--s-4); grid-template-columns:repeat(auto-fill, minmax(272px, 1fr)); }
.post{
  display:flex; flex-direction:column; overflow:hidden;
  transition:transform .2s var(--ease-out), box-shadow .2s var(--ease-out);
}
.post:hover{ transform:translateY(-4px); box-shadow:var(--e-3); }
.post__link{ display:flex; flex-direction:column; block-size:100%; }
.post__art{ aspect-ratio:16 / 10; flex:none; }
/* Fires from anywhere on the card, not just the art — the whole tile is one
   link, so the whole tile is one hover target. */
.post:hover .sky__comet, .nova:hover .sky__comet{ transform:rotate(19deg) translateX(330%); }
.post__body{ display:flex; flex-direction:column; gap:var(--s-2); padding:var(--s-4); flex:1; }
.post__title{ font-size:var(--t-base); font-weight:700; line-height:1.75; }
.post:hover .post__title{ color:var(--accent); }
.post__excerpt{
  margin:0; font-size:var(--t-sm); color:var(--text-muted); line-height:1.9;
  /* Three lines, then it stops. Ragged card heights across a grid make the row
     baselines jump and the whole section look unaligned. */
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; overflow:hidden;
}
.post__meta{
  margin:auto 0 0; padding-block-start:var(--s-3);
  display:flex; align-items:center; gap:var(--s-2);
  font-size:.75rem; color:var(--text-muted);
  border-block-start:1px solid var(--line);
}
.post__meta .sep{ opacity:.45; }
.post__read{ margin-inline-start:auto; display:inline-flex; align-items:center; gap:5px; }
.post__read svg{ inline-size:13px; block-size:13px; fill:none; stroke:currentColor; stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round; }

.posts__empty{ text-align:center; color:var(--text-muted); padding-block:var(--s-7); }

/* ═════════════════ 6. the reading path ═════════════════
   Five articles in the order they are worth reading, drawn as a constellation.
   The connector is a real polyline measured from the laid-out nodes in JS, so
   it survives wrapping, RTL and any number of steps. */
.path{ margin-block:var(--s-9) 0; }
.path__field{ position:relative; padding-block:var(--s-5); }
.path__line{ position:absolute; inset:0; inline-size:100%; block-size:100%; pointer-events:none; overflow:visible; }
.path__line polyline{
  fill:none; stroke:var(--star); stroke-width:1.4; opacity:.22;
  stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:var(--len) var(--len); stroke-dashoffset:var(--len);
  animation:draw 2.4s var(--ease-out) .2s forwards;
}
@keyframes draw{ to{ stroke-dashoffset:0; } }
.path__nodes{
  position:relative; z-index:1;
  list-style:none; margin:0; padding:0;
  display:grid; gap:var(--s-4); grid-template-columns:repeat(auto-fit, minmax(184px, 1fr));
}
/* The vertical stagger is what turns a row of boxes into a star chart. It is a
   fixed pattern rather than a random one so the connector never doubles back. */
.path__nodes li{ transform:translateY(var(--dy, 0px)); }
.step{
  display:flex; flex-direction:column; gap:6px; block-size:100%;
  padding:var(--s-4); border-radius:var(--r-md);
  transition:transform .2s var(--ease-out), box-shadow .2s var(--ease-out), border-color .2s var(--ease-out);
}
.step:hover{ transform:translateY(-3px); box-shadow:var(--e-2); border-color:color-mix(in oklch, var(--accent) 42%, var(--pane-line)); }
.step__star{
  inline-size:22px; block-size:22px; background:var(--accent);
  -webkit-mask:var(--star-mask) center/contain no-repeat;
          mask:var(--star-mask) center/contain no-repeat;
  filter:drop-shadow(0 0 6px color-mix(in oklch, var(--accent) 55%, transparent));
}
.step__n{ font-size:.6875rem; font-weight:700; color:var(--text-muted); }
.step__title{ font-size:var(--t-sm); font-weight:700; line-height:1.7; }
.step__time{ margin-block-start:auto; padding-block-start:6px; font-size:.6875rem; color:var(--text-muted); }

/* ═════════════════ 9. mobile ═════════════════ */
@media (max-width:900px){
  .galaxy{ min-block-size:auto; padding-block:var(--s-6) var(--s-4); }
  .galaxy__sky{
    inset-block-start:0; left:50%;
    inline-size:min(760px, 165%);
    transform:translate(-50%, -12%);
    /* Behind the copy now instead of beside it, so it has to give way. */
    opacity:calc(var(--galaxy-op) * .55);
  }
  .galaxy__stats{ gap:var(--s-5); }
  .nova__art{ min-block-size:220px; }
  /* Two per row on a tablet: a single-column list halves how many titles get
     seen per scroll, which on an archive is the whole job. Below 560px it does
     drop to one — an article card is mostly words, and a 160px column turns
     every title into five ragged lines. */
  .posts{ grid-template-columns:1fr 1fr; gap:var(--s-3); }
  .post__body{ padding:var(--s-3); gap:6px; }
  .post__title{ font-size:.8125rem; line-height:1.65; }
  .post__excerpt{ -webkit-line-clamp:2; font-size:.75rem; }
  .post__meta{ font-size:.6875rem; }
  .path__nodes li{ transform:none; }   /* the stagger needs a wide row to read */
  .path__line{ display:none; }
}
@media (max-width:560px){
  .posts{ grid-template-columns:1fr; }
  .path__nodes{ grid-template-columns:1fr; }
}

/* ═════════════════ 10. reduced motion ═════════════════ */
@media (prefers-reduced-motion: reduce){
  .galaxy__shell, .nova__system, .nova__spin, .nova__moon, .nova__orbit{ animation:none; }
  /* The moon still has to land somewhere sensible with the spin off — in front
     of the planet, on the near half of the ring. */
  .nova__orbit{ z-index:3; }
  .path__line polyline{ animation:none; stroke-dashoffset:0; }
  .sky__comet{ display:none; }
}
