﻿/* =========================================================
   Gem Store — product page (glass)
   Rides on style.css: same tokens, same nav, same starfield.
   Everything new here is scoped under .page-product / .pdp so
   it can never leak back into the home page.
   ========================================================= */

/* ---------------- glass tokens ----------------
   Two surfaces, not one. `--glass-*` is for panels that sit directly on the
   page and really do blur the backdrop. `--glass-inner` is for boxes nested
   INSIDE those panels — their backdrop is already blurred, so a second
   backdrop-filter buys nothing visible and costs a full extra pass. On a page
   with sixteen glass-looking boxes that difference is the whole frame budget. */
:root, :root[data-theme="light"]{
  --glass-bg:      rgba(255,255,255,.58);
  --glass-solid:   rgba(255,255,255,.94);
  --glass-line:    rgba(255,255,255,.72);
  --glass-inner:   rgba(255,255,255,.52);
  --glass-inner-h: rgba(255,255,255,.78);
  /* On white, a white translucent panel has no edge. The hairline ring in the
     middle layer is what gives it one; the inset is the light catching the top. */
  --glass-shadow:  var(--e-2),
                   0 0 0 1px rgba(11,11,16,.055),
                   inset 0 1px 0 rgba(255,255,255,.9);
  --glass-sheen:   .55;
  --aurora-op:     .34;
  /* Constellation ink. On a white page an unlit star has to be genuinely dark
     or the letterforms dissolve; on black the same floor would leave them
     glowing when nothing is shining on them. product.js reads these two and
     maps its 0…1 lit value onto base + lit × gain. */
  --cst-base: .52; --cst-gain: .48;
  --seg-base: .34; --seg-gain: .46;
  --cst-glow: .30;
  --moon-body: #2C2B3A;   /* the unlit half stays dark in daylight too */
}
:root[data-theme="dark"]{
  --glass-bg:      rgba(27,26,41,.48);
  --glass-solid:   rgba(20,19,31,.96);
  --glass-line:    rgba(255,255,255,.10);
  --glass-inner:   rgba(255,255,255,.045);
  --glass-inner-h: rgba(255,255,255,.085);
  --glass-shadow:  var(--e-2),
                   0 0 0 1px rgba(255,255,255,.045),
                   inset 0 1px 0 rgba(255,255,255,.085);
  --glass-sheen:   .10;
  --aurora-op:     .42;
  --cst-base: .18; --cst-gain: .82;
  --seg-base: .09; --seg-gain: .52;
  --cst-glow: .55;
  --moon-body: #1B1A29;
}

.page-product{
  --glass-blur: 22px;
  --t-price: clamp(1.65rem, 1.05rem + 2.6vw, 2.6rem);
}

.glass{
  position:relative;
  background:var(--glass-bg);
  backdrop-filter:blur(var(--glass-blur)) saturate(165%);
  -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(165%);
  border:1px solid var(--glass-line);
  border-radius:var(--r-lg);
  box-shadow:var(--glass-shadow);
}
/* The rim light. A single diagonal wash reads as a sheet of glass lit from the
   top-start corner; without it the panel is just a tinted rectangle. */
.glass::before{
  content:''; position:absolute; inset:0; z-index:0;
  border-radius:inherit; pointer-events:none;
  background:linear-gradient(150deg,
    rgba(255,255,255,var(--glass-sheen)) 0%,
    rgba(255,255,255,calc(var(--glass-sheen) * .22)) 28%,
    transparent 55%);
}
.glass > *{ position:relative; z-index:1; }

/* Same surface, no backdrop pass. The small repeated tiles — specs, the
   included list, the FAQ rows — sit over the aurora and the starfield, both of
   which are already soft, so blurring behind them changes almost nothing on
   screen while multiplying the most expensive effect on the page. Real
   backdrop-filter is spent only on the three large panels. */
.glass--lite{
  backdrop-filter:none; -webkit-backdrop-filter:none;
  background:color-mix(in srgb, var(--glass-bg) 70%, var(--surface));
}

/* Safari <18 on some Android builds, and every browser with the feature off:
   fall back to an opaque surface rather than a see-through box with unreadable
   text over the starfield. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .glass{ background:var(--glass-solid); }
}

/* ---------------- page shell ----------------
   .pdp opens its own stacking context so the aurora and the panels can be
   layered against each other without any of them out-painting the sticky nav
   (which sits at z-index 1 in the root context). */
.pdp{
  position:relative; z-index:0;
  max-width:1240px; margin-inline:auto;
  padding:var(--s-5) var(--s-5) var(--s-9);
  --accent:var(--indigo);
}
.pdp > *:not(.aurora){ position:relative; z-index:1; }

/* ---------------- aurora ---------------- */
.aurora{ position:absolute; inset:-10% -5% auto; block-size:min(1100px, 120%); z-index:0; pointer-events:none; overflow:hidden; }
.aurora__blob{
  position:absolute; display:block;
  inline-size:clamp(280px, 42vw, 620px); aspect-ratio:1;
  border-radius:50%; filter:blur(70px); opacity:var(--aurora-op);
  will-change:transform;
}
.aurora__blob--a{ inset-block-start:-6%; inset-inline-end:-4%;  background:radial-gradient(circle, var(--accent) 0%, transparent 68%); animation:drift-a 22s ease-in-out infinite; }
.aurora__blob--b{ inset-block-start:22%; inset-inline-start:-8%; background:radial-gradient(circle, var(--cyan) 0%, transparent 68%);   animation:drift-b 27s ease-in-out infinite; }
.aurora__blob--c{ inset-block-start:52%; inset-inline-end:18%;   background:radial-gradient(circle, var(--pink) 0%, transparent 68%);   animation:drift-c 31s ease-in-out infinite; opacity:calc(var(--aurora-op) * .7); }
@keyframes drift-a{ 50%{ transform:translate(-6%, 8%) scale(1.12); } }
@keyframes drift-b{ 50%{ transform:translate(7%, -6%) scale(.92); } }
@keyframes drift-c{ 50%{ transform:translate(-4%, -9%) scale(1.08); } }

/* ---------------- breadcrumb ---------------- */
.crumb{
  display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
  font-size:var(--t-sm); color:var(--text-muted);
  padding-block:var(--s-4) var(--s-6);
}
.crumb a{ transition:color .18s var(--ease-out); }
.crumb a:hover{ color:var(--ink); }
.crumb span{ color:var(--ink); font-weight:700; }
/* The chevron points the way the text runs, so it flips with the document. */
.crumb svg{ inline-size:14px; block-size:14px; flex:none; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; opacity:.5; }

/* ---------------- top grid ---------------- */
.pdp__top{ display:grid; gap:var(--s-6); grid-template-columns:minmax(0,1fr); align-items:start; }
@media (min-width:1000px){
  .pdp__top{ grid-template-columns:minmax(0,1fr) minmax(0,1.05fr); gap:var(--s-7); }
  /* Sticky so the plan boxes stay reachable while the page below is read. */
  .showcase{ position:sticky; inset-block-start:calc(var(--s-8) + var(--s-3)); }
}

/* ---------------- showcase ---------------- */
.showcase{ display:grid; gap:var(--s-4); min-inline-size:0; }

/* ONE ratio for every viewport. The old min-block-size:clamp(…,40vh,…) made the
   frame a different shape on a phone than on a laptop, so the same uploaded
   image was cropped differently on each — which is exactly what you notice and
   can't fix from the media library. A fixed aspect-ratio takes the decision
   away from the viewport: change --frame-ratio here and it changes everywhere.
   Match Woo's catalogue image ratio to this and set its crop to hard. */
.page-product{ --frame-ratio: 16 / 9; }
.frame{
  aspect-ratio:var(--frame-ratio);
  padding:0; overflow:hidden;
}
/* Both layers are taken OUT of flow on purpose. In flow, a child sized
   block-size:100% inside an aspect-ratio box is circular — the row sizes to the
   child, the child sizes to the row — and the browser resolves it by letting
   the image win, which is how the frame ends up a different shape at every
   width. Absolute children can't contribute to the box's automatic minimum
   size, so the ratio is the only thing deciding. */
.frame__img{
  position:absolute; inset:0;
  inline-size:100%; block-size:100%;
  object-fit:cover;                 /* the frame crops, never the layout */
  object-position:center;
  display:block;
}
/* ── the swap ──
   Layers cross-fade; only the one carrying .is-on is visible. The incoming
   shot arrives from slightly too big and too bright and settles, which reads as
   something falling INTO the frame rather than a slide changing. */
.frame__img{
  opacity:0; transform:scale(1.10);
  filter:blur(16px) brightness(1.7) saturate(1.4);
  transition:opacity .5s var(--ease-out), transform .72s var(--ease-out), filter .5s var(--ease-out);
}
.frame__img.is-on{ opacity:1; transform:none; filter:none; }
/* The one on its way out falls away instead of mirroring the entrance — two
   layers running the same animation backwards just look like a cross-fade. */
.frame__img.is-off{ opacity:0; transform:scale(.9) rotate(-1.5deg); filter:blur(12px) brightness(.6); }

/* The shockwave. One ring, expanding past the frame edge and gone in half a
   second — long enough to register as a jump, short enough not to be a wait. */
.frame__warp{
  position:absolute; inset-block-start:50%; inset-inline-start:50%;
  inline-size:34%; aspect-ratio:1; border-radius:50%;
  transform:translate(50%,-50%) scale(.2);
  border:2px solid color-mix(in oklch, var(--accent) 70%, white);
  box-shadow:0 0 40px color-mix(in oklch, var(--accent) 60%, transparent),
             inset 0 0 30px color-mix(in oklch, var(--accent) 40%, transparent);
  opacity:0; pointer-events:none;
}
.frame.is-warping .frame__warp{ animation:warp .62s var(--ease-out); }
@keyframes warp{
  0%   { opacity:0;   transform:translate(50%,-50%) scale(.15); }
  22%  { opacity:.95; }
  100% { opacity:0;   transform:translate(50%,-50%) scale(3.4); }
}

/* Star dust thrown outward from the middle. The spans are generated once in JS
   with a per-particle angle, distance and delay, so the burst never repeats the
   same pattern twice in a row. */
.frame__dust{ position:absolute; inset:0; pointer-events:none; }
.frame__dust i{
  position:absolute; inset-block-start:50%; inset-inline-start:50%;
  inline-size:var(--sz,3px); aspect-ratio:1; border-radius:50%;
  background:var(--sun-hot, #fff);
  box-shadow:0 0 8px color-mix(in oklch, var(--accent) 80%, white);
  opacity:0;
}
.frame.is-warping .frame__dust i{ animation:dust .72s var(--ease-out) var(--d,0s) both; }
@keyframes dust{
  0%   { opacity:0; transform:translate(0,0) scale(.4); }
  18%  { opacity:1; }
  100% { opacity:0; transform:translate(var(--dx), var(--dy)) scale(.2); }
}

/* The accent of the selected plan washed over the photo. soft-light tints
   without flattening it, so a five-plan product still reads as five distinct
   choices from the image alone. */
.frame__tint{
  position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(circle at 28% 22%, var(--accent) 0%, transparent 72%);
  mix-blend-mode:soft-light; opacity:.62;
  transition:background .4s var(--ease-out);
}
/* The glass sheen belongs ON TOP of the photo here, not under it. */
.frame::before{ z-index:2; }

.frame__chip{
  position:absolute; z-index:2;
  padding:6px var(--s-3); border-radius:var(--r-full);
  font-size:var(--t-sm); font-weight:700; line-height:1.6;
  background:var(--glass-inner-h);
  border:1px solid var(--glass-line);
  box-shadow:var(--e-1);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
}
.frame__chip{ z-index:3; }   /* above the sheen */
.frame__chip--tier{ inset-block-start:var(--s-4); inset-inline-start:var(--s-4); color:var(--accent); transition:color .3s var(--ease-out); }
.frame__chip--term{ inset-block-end:var(--s-4); inset-inline-end:var(--s-4); color:var(--text-muted); }

/* ---------------- gallery thumbnails ---------------- */
.shots-wrap{ position:relative; display:grid; justify-items:center; padding-block:var(--s-3); }
/* Fixed 400×110 and centred rather than stretched to the column: the strip is
   a fixed 208px wide, and scaling the box with the column would squash the
   stars out of round on a phone. */
.shots__stars{
  position:absolute; inset-block-start:50%; left:50%;
  inline-size:400px; block-size:110px;
  transform:translate(-50%, -50%);
  pointer-events:none; overflow:visible;
}
.shots{ position:relative; z-index:1; display:flex; justify-content:center; gap:var(--s-2); flex-wrap:wrap; }
.shots button{
  inline-size:64px; aspect-ratio:var(--frame-ratio);
  padding:0; border-radius:10px; overflow:hidden; cursor:pointer;
  border:1px solid var(--glass-line); background:var(--glass-inner);
  box-shadow:var(--e-1);
  opacity:.55; transition:opacity .18s var(--ease-out), transform .18s var(--ease-out), border-color .18s var(--ease-out);
}
.shots img{ inline-size:100%; block-size:100%; object-fit:cover; display:block; }
.shots button:hover{ opacity:.85; transform:translateY(-2px); }
.shots button[aria-selected="true"]{
  opacity:1; border-color:color-mix(in oklch, var(--accent) 65%, transparent);
  box-shadow:0 0 0 1px color-mix(in oklch, var(--accent) 55%, transparent), var(--e-1);
}

/* ---------------- spec tiles ---------------- */
.specs{ list-style:none; margin:0; padding:0; display:grid; gap:var(--s-2); grid-template-columns:repeat(3,minmax(0,1fr)); }
.specs li{ display:grid; justify-items:center; text-align:center; gap:2px; padding:var(--s-4) var(--s-2); border-radius:var(--r-md); }
  /* color و نه stroke — symbol خودش stroke="currentColor" دارد و صفت
     نمایشی از مقدار به‌ارث‌رسیده قوی‌تر است. */
.specs svg{ inline-size:20px; block-size:20px; margin-block-end:4px; color:var(--accent); transition:color .3s var(--ease-out); }
.specs strong{ font-size:var(--t-sm); font-weight:900; }
.specs span{ font-size:.75rem; color:var(--text-muted); line-height:1.7; }

/* ---------------- buy panel ---------------- */
.buy{ display:grid; gap:var(--s-5); padding:clamp(var(--s-4), 2.4vw, var(--s-6)); min-inline-size:0; }
.buy__head{ display:grid; gap:var(--s-3); }
.buy__badge{
  justify-self:start; display:inline-flex; align-items:center; gap:var(--s-2);
  margin:0; padding:5px var(--s-3); border-radius:var(--r-full);
  background:var(--glass-inner); border:1px solid var(--glass-line);
  font-size:var(--t-sm); color:var(--emerald);
}
.buy h1{ font-size:var(--t-xl); font-weight:900; }
.buy__lede{ margin:0; color:var(--text-muted); font-size:var(--t-sm); max-inline-size:48ch; }

/* ---------------- option groups ---------------- */
.opts{ border:0; margin:0; padding:0; min-inline-size:0; }
.opts__title{
  padding:0; display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s-3);
  margin-block-end:var(--s-3); font-size:var(--t-sm); font-weight:900;
}
.opts__hint{ font-weight:400; font-size:.75rem; color:var(--text-muted); }
/* Plans stack as full-width rows. Side-by-side cards look good at two plans and
   fall apart at five — 112px of column can't hold a name, a line of copy and a
   price. Rows take any count from two to eight without a breakpoint, and they
   put every plan's rate in the same place so the list is actually comparable. */
.opts__rows{ display:grid; gap:var(--s-2); }

/* Durations are short labels, so they do tile. auto-fit collapses the tracks it
   can't fill, so three terms give three equal columns and six give three plus
   three — the count is never written down anywhere. */
/* 90px is chosen, not guessed: it is the largest floor that still fits three
   tracks across a 375px phone, so the three remaining durations make one full
   row there instead of two-and-a-gap. auto-fit collapses what it can't fill, so
   the same rule gives three equal columns on desktop and wraps cleanly at five
   or six durations. */
.opts__grid{ display:grid; gap:var(--s-3); grid-template-columns:repeat(auto-fit, minmax(90px, 1fr)); }

/* The best-selling duration gets its own full-width row above the tiles. Making
   it a wide CELL instead keeps the tracks it spans reserved on the row below,
   so the remaining durations end up with a hole at the end of their row. Out of
   the grid, it's the same emphasis with no leftovers at any duration count. */
.opts__lead{ display:grid; margin-block-end:var(--s-3); }
.opts__lead:empty{ display:none; margin-block-end:0; }
.opt--term.is-featured .opt__box{
  grid-template-columns:auto minmax(0,1fr) auto auto;
  align-items:center; text-align:start; justify-items:start;
  gap:var(--s-3); padding:var(--s-3) var(--s-4);
}
.opt--term.is-featured .opt__text{ display:grid; gap:0; min-inline-size:0; }
.opt--term.is-featured .opt__name{ font-size:var(--t-base); }
.opt--term.is-featured .opt__per{ text-align:start; }
.opt--term.is-featured .opt__off{ margin-block-start:0; }
/* The tick moves into the flow here — pinned to a corner it would sit miles
   from the label on a box this wide. */
.opt--term.is-featured .opt__check{ position:static; inline-size:20px; block-size:20px; }
/* The badge that says why it's wider. */
/* ترکیب ناموجود — کم‌رنگ ولی همچنان قابل انتخاب، تا کاربر بتواند
   انتخاب دیگرش را عوض کند و ببیند کدام ترکیب باز می‌شود. رنگ به‌تنهایی
   حامل معنا نیست: خط‌خوردگی قیمت هم اضافه می‌شود (قانون ۱۰ ui-ux-pro-max). */
.opt.is-unavailable{ opacity:.42; }
.opt.is-unavailable .opt__rate,
.opt.is-unavailable .opt__per{ text-decoration:line-through; }
.opt.is-unavailable .opt__off{ display:none; }

.opt__hot{
  padding:1px var(--s-2); border-radius:var(--r-full);
  font-size:.625rem; font-weight:700; line-height:1.7;
  background:var(--ink); color:var(--on-ink); white-space:nowrap;
}
@media (max-width:420px){
  .opt--term.is-featured .opt__box{ grid-template-columns:auto minmax(0,1fr) auto; row-gap:var(--s-2); }
  .opt--term.is-featured .opt__off{ grid-column:2; }
}

/* A real <input type="radio"> under there: arrow-key navigation inside the
   group, screen-reader announcement, and form semantics all come free. It stays
   in the layout (not display:none) so it can still take focus. */
.opt{ position:relative; display:grid; min-inline-size:0; cursor:pointer; }
.opt input{ position:absolute; inset-block-start:0; inset-inline-start:0; inline-size:1px; block-size:1px; opacity:0; margin:0; }

.opt__box{
  display:grid; gap:2px; align-content:start;
  padding:var(--s-4) var(--s-4) var(--s-3);
  border-radius:var(--r-md);
  background:var(--glass-inner);
  border:1px solid var(--glass-line);
  box-shadow:inset 0 1px 0 rgba(255,255,255,calc(var(--glass-sheen) * .8));
  transition:background .18s var(--ease-out), border-color .18s var(--ease-out),
             transform .18s var(--ease-out), box-shadow .18s var(--ease-out);
}
/* گزینه‌های انتخاب‌نشده در تم روشن.
   --glass-inner و --glass-line هر دو سفیدِ نیمه‌شفاف‌اند؛ روی پس‌زمینه‌ی
   روشن یعنی سفید روی سفید، و کادرهای «۳ ماهه/۶ ماهه/۱۲ ماهه» عملاً در
   زمینه گم می‌شدند. در تم تاریک همین توکن‌ها سفیدِ ۴٪ روی زمینه‌ی تیره‌اند
   و کار می‌کنند — پس مشکل فقط مال حالت روشن است و اصلاح هم فقط همان‌جا
   اعمال می‌شود. یک سطح و قابِ خاکستریِ ملایم، بدون رنگ، تا با حالتِ
   انتخاب‌شده (که اکسنت رنگی می‌گیرد) اشتباه گرفته نشود. */
:root:not([data-theme="dark"]) .opt input:not(:checked) + .opt__box{
  background:rgba(17,17,24,.035);
  border-color:rgba(17,17,24,.12);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.6);
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .opt input:not(:checked) + .opt__box{
    background:var(--glass-inner);
    border-color:var(--glass-line);
    box-shadow:inset 0 1px 0 rgba(255,255,255,calc(var(--glass-sheen) * .8));
  }
}
@media (hover:hover) and (pointer:fine){
  .opt:hover .opt__box{ background:var(--glass-inner-h); transform:translateY(-2px); }
}
.opt input:focus-visible + .opt__box{ outline:2px solid var(--ink); outline-offset:3px; }

.opt input:checked + .opt__box{
  background:color-mix(in oklch, var(--accent) 12%, var(--glass-inner-h));
  border-color:color-mix(in oklch, var(--accent) 55%, transparent);
  box-shadow:0 0 0 1px color-mix(in oklch, var(--accent) 45%, transparent),
             0 8px 22px color-mix(in oklch, var(--accent) 22%, transparent),
             inset 0 1px 0 rgba(255,255,255,calc(var(--glass-sheen) * .9));
}

.opt__name{ font-weight:900; font-size:var(--t-base); }
.opt__meta{ font-size:.75rem; color:var(--text-muted); line-height:1.75; }

/* ── a plan row ── */
.opt--plan .opt__box{
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center; gap:var(--s-3);
  padding:var(--s-3) var(--s-4);
}
.opt__text{ display:grid; gap:1px; min-inline-size:0; }
.opt__radio{
  inline-size:19px; block-size:19px; border-radius:50%;
  border:2px solid var(--line); background:transparent;
  display:grid; place-items:center;
  transition:border-color .18s var(--ease-out);
}
.opt__radio::after{
  content:''; inline-size:9px; block-size:9px; border-radius:50%;
  background:var(--accent); transform:scale(0);
  transition:transform .18s var(--ease-out), background-color .3s var(--ease-out);
}
.opt input:checked + .opt__box .opt__radio{ border-color:var(--accent); }
.opt input:checked + .opt__box .opt__radio::after{ transform:scale(1); }

.opt__rate{ font-size:.75rem; color:var(--text-muted); text-align:end; white-space:nowrap; }
.opt__rate b{ display:block; font-weight:900; color:var(--text); font-size:var(--t-sm); }
.opt__na{ color:var(--text-muted); font-weight:400; }

/* Under ~480px the rate drops to its own line rather than squeezing the name
   into two words per row. */
@media (max-width:480px){
  .opt--plan .opt__box{ grid-template-columns:auto minmax(0,1fr); row-gap:var(--s-2); }
  .opt__rate{ grid-column:2; text-align:start; }
  .opt__rate b{ display:inline; }
}

/* An unsold combination is dimmed, not removed: the option still reads as a
   real thing the shop could sell, which is the difference between "Business
   isn't monthly" and "the page is broken". */
.opt input:disabled + .opt__box{ opacity:.42; }
.opt:has(input:disabled){ cursor:not-allowed; }
@media (hover:hover) and (pointer:fine){
  .opt:has(input:disabled):hover .opt__box{ background:var(--glass-inner); transform:none; }
}

.opt__check{
  position:absolute; inset-block-start:var(--s-3); inset-inline-end:var(--s-3);
  inline-size:20px; block-size:20px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--accent); color:var(--on-ink);
  opacity:0; transform:scale(.5);
  transition:opacity .18s var(--ease-out), transform .18s var(--ease-out), background-color .3s var(--ease-out);
}
:root[data-theme="dark"] .opt__check{ color:#0B0B10; }
.opt__check svg{ inline-size:12px; block-size:12px; fill:none; stroke:currentColor; stroke-width:3; stroke-linecap:round; stroke-linejoin:round; }
.opt input:checked + .opt__box .opt__check{ opacity:1; transform:scale(1); }

/* Inline with the name, not pinned to a corner: a row has no corner to spare,
   and an absolutely-placed badge would collide the moment a plan name got long. */
.opt__flag{
  display:inline-block; margin-inline-start:var(--s-2);
  padding:1px var(--s-2); border-radius:var(--r-full);
  font-size:.625rem; font-weight:700; line-height:1.7; vertical-align:middle;
  background:var(--ink); color:var(--on-ink);
}

/* ── term boxes are narrower, so they get their own rhythm ── */
.opt--term .opt__box{ padding:var(--s-4) var(--s-3) var(--s-3); text-align:center; justify-items:center; gap:1px; }
.opt--term .opt__check{ inset-block-start:6px; inset-inline-end:6px; inline-size:17px; block-size:17px; }
.opt--term .opt__name{ font-size:var(--t-sm); }
.opt--term .opt__per{ font-size:.6875rem; color:var(--text-muted); line-height:1.7; }
.opt--term .opt__per b{ font-weight:900; color:var(--text); }
/* min-block-size, not content, reserves the row: the undiscounted term renders
   an EMPTY badge, and an empty grid item collapses to nothing. Without the
   reservation that one box comes out shorter than the other three. */
.opt--term .opt__off{
  margin-block-start:6px; padding:1px var(--s-2); border-radius:var(--r-full);
  min-block-size:calc(1.7em + 2px);
  font-size:.6875rem; font-weight:700; line-height:1.7;
  background:color-mix(in oklch, var(--emerald) 16%, transparent);
  color:var(--emerald);
}

/* ---------------- price ---------------- */
.price{
  display:grid; gap:2px;
  padding:var(--s-4);
  border-radius:var(--r-md);
  background:var(--glass-inner-h);
  border:1px solid var(--glass-line);
  box-shadow:inset 0 1px 0 rgba(255,255,255,calc(var(--glass-sheen) * .9));
}
.price__row{ display:flex; align-items:baseline; gap:var(--s-3); flex-wrap:wrap; }
.price__label{ font-size:var(--t-sm); color:var(--text-muted); }
.price__was{ color:var(--text-muted); opacity:.75; font-size:var(--t-sm); text-decoration-thickness:1.5px; }
.price__now{ margin:0; line-height:1.35; }
.price__now b{ font-size:var(--t-price); font-weight:900; letter-spacing:-.01em; }
.price__now span{ font-size:var(--t-base); font-weight:700; color:var(--text-muted); margin-inline-start:6px; }
.price__per{ margin:0; font-size:var(--t-sm); color:var(--text-muted); }
.price__per b{ font-weight:900; color:var(--text); }
/* Blanked with .is-blank on the terms that have no discount — and the row is
   reserved with min-block-size, because visibility alone still lets an EMPTY
   box collapse to its padding. Without the reservation the whole panel, and
   the button under it, shift 23px as you move between terms. */
.price__save{
  margin:var(--s-2) 0 0; justify-self:start;
  padding:3px var(--s-3); border-radius:var(--r-full);
  min-block-size:calc(1.9em + 6px);
  font-size:.75rem; font-weight:700;
  background:color-mix(in oklch, var(--emerald) 16%, transparent); color:var(--emerald);
}
/* visibility, not display: what is reserved above has to stay reserved. */
.is-blank{ visibility:hidden; }

/* ---------------- cta ---------------- */
.buy__cta{ inline-size:100%; min-block-size:52px; font-size:var(--t-base); }
.buy__cta svg{ inline-size:19px; block-size:19px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.buy__cta.is-done{ background:var(--emerald); }
.buy__note{ margin:0; font-size:.75rem; color:var(--text-muted); line-height:1.9; }

/* ---------------- what's included ---------------- */
.incl{ padding-block:var(--s-9) 0; }

/* Plan tabs. They drive the same radio group as the buy panel, so the page
   never holds two different answers to "which plan?" — clicking Pro here shows
   Pro's list AND moves the price above. One selection, two places to make it. */
.incl__tabs{
  display:flex; flex-wrap:wrap; justify-content:center; gap:var(--s-2);
  margin-block-end:var(--s-5);
}
.incl__tabs button{
  min-block-size:44px; padding-inline:var(--s-5);
  border-radius:var(--r-full); cursor:pointer;
  font-family:inherit; font-size:var(--t-sm); font-weight:700; color:var(--text-muted);
  background:var(--glass-inner); border:1px solid var(--glass-line);
  box-shadow:inset 0 1px 0 rgba(255,255,255,calc(var(--glass-sheen) * .8));
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  transition:color .18s var(--ease-out), background .18s var(--ease-out),
             border-color .18s var(--ease-out), transform .18s var(--ease-out);
}
.incl__tabs button:hover{ color:var(--ink); transform:translateY(-1px); }
.incl__tabs button[aria-selected="true"]{
  color:var(--ink);
  background:color-mix(in oklch, var(--accent) 14%, var(--glass-inner-h));
  border-color:color-mix(in oklch, var(--accent) 55%, transparent);
  box-shadow:0 0 0 1px color-mix(in oklch, var(--accent) 45%, transparent),
             0 8px 22px color-mix(in oklch, var(--accent) 20%, transparent);
}
.incl__tabs button[disabled]{ opacity:.42; cursor:not-allowed; transform:none; }
.incl__grid{ list-style:none; margin:0; padding:0; display:grid; gap:var(--s-3); grid-template-columns:minmax(0,1fr); }
@media (min-width:700px){ .incl__grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
.incl__grid li{ display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--s-3); align-items:start; padding:var(--s-4); border-radius:var(--r-md); }
  /* color و نه stroke — symbol خودش stroke="currentColor" دارد و صفت
     نمایشی از مقدار به‌ارث‌رسیده قوی‌تر است. */
.incl__grid svg{ inline-size:18px; block-size:18px; margin-block-start:5px; color:var(--accent); transition:color .3s var(--ease-out); }
.incl__grid strong{ display:block; font-weight:900; font-size:var(--t-sm); }
.incl__grid span{ font-size:.8125rem; color:var(--text-muted); line-height:1.85; }
#inclTier{ color:var(--accent); transition:color .3s var(--ease-out); }

/* ---------------- faq ---------------- */
.faq{ padding-block:var(--s-9) 0; }
.faq__list{ display:grid; gap:var(--s-3); max-width:820px; margin-inline:auto; }
.faq details{ border-radius:var(--r-md); overflow:hidden; }
/* پرسش‌های متداول با یک پله فونت بزرگ‌تر. این بخش هم برای خواننده و هم
   برای موتورهای پاسخ‌گو (اسکیمای FAQPage از همین متن ساخته می‌شود) مهم است
   و در اندازه‌ی --t-sm ریزتر از اهمیتش دیده می‌شد. */
.faq summary{
  display:flex; align-items:center; justify-content:space-between; gap:var(--s-4);
  padding:var(--s-4); cursor:pointer; list-style:none;
  font-weight:700; font-size:var(--t-base); line-height:1.7;
}
.faq summary::-webkit-details-marker{ display:none; }
.faq__ico{ position:relative; inline-size:16px; block-size:16px; flex:none; }
.faq__ico::before, .faq__ico::after{
  content:''; position:absolute; inset-block-start:50%; inset-inline-start:0;
  inline-size:16px; block-size:1.8px; border-radius:2px; background:var(--text-muted);
  transition:transform .22s var(--ease-out);
}
.faq__ico::after{ transform:rotate(90deg); }
.faq details[open] .faq__ico::after{ transform:rotate(0deg); }
.faq details p{
  margin:0; padding:0 var(--s-4) var(--s-4);
  color:var(--text-muted); font-size:var(--t-base); line-height:1.95;
}

/* ---------------- sticky mobile buy bar ---------------- */
.buybar{
  position:fixed; inset-block-end:0; inset-inline:0; z-index:5;
  display:none; align-items:center; justify-content:space-between; gap:var(--s-4);
  padding:var(--s-3) var(--s-4) calc(var(--s-3) + env(safe-area-inset-bottom));
  background:var(--nav-bg);
  backdrop-filter:blur(18px) saturate(160%); -webkit-backdrop-filter:blur(18px) saturate(160%);
  border-block-start:1px solid var(--line);
  transform:translateY(110%);
  transition:transform .28s var(--ease-out);
}
.buybar.is-on{ transform:none; }
.buybar__info{ display:grid; min-inline-size:0; }
/* نوار خرید چسبان: ۱۱ پیکسلِ muted در لحظه‌ی تصمیمِ خرید خوانده نمی‌شود.
   ۱۲ پیکسل حداقلی است که هنوز فرعی به نظر می‌رسد. */
.buybar__meta{ font-size:.75rem; color:var(--text-muted); }
.buybar__price{ font-size:var(--t-base); font-weight:900; white-space:nowrap; }
.buybar__price b{ font-weight:900; }
.buybar .btn{ flex:none; }
@media (max-width:999px){
  .buybar{ display:flex; }
  /* Room so the bar never covers the last line of the FAQ. */
  .foot{ padding-block-end:calc(var(--s-8) + var(--s-5)); }
}

/* ---------------- the orbit ----------------
   Sun and moon turning about their shared centre. Same palette tokens as the
   star on the home page, so the two pages read as one place. */
.orbit{ margin:0; padding:var(--s-3); display:grid; gap:var(--s-3); }
/* On desktop it reaches out past the column's padding — the figure is the one
   thing in this column that gains from every pixel of width, and the sticky
   column has the room to give. */
@media (min-width:1000px){
  .orbit{ margin-inline:calc(var(--s-4) * -1); }
}
/* On a phone it sits at the very end of the page, so it gets air above it
   rather than butting straight up against the last FAQ row. */
@media (max-width:999px){
  .orbit{ margin-block-start:var(--s-7); }
}
/* Clipped, not visible: star blooms scale with how lit they are, and the widest
   of them would otherwise paint out over the caption underneath. */
.orbit__sky{ inline-size:100%; block-size:auto; display:block; overflow:hidden; }
/* The caption rides the same ink, so it crosses over with the rest of the panel
   during the dawn instead of staying the page's colour on an inverted card. */
.orbit__cap{ display:grid; gap:2px; text-align:center; color:var(--orb-ink); }
.orbit__cap strong{ font-size:var(--t-sm); font-weight:900; }
.orbit__cap span{ font-size:.75rem; opacity:.66; line-height:1.8; }

.orbit .halo{ opacity:var(--sun-glow); }
/* Filled plasma shells rather than outlines — a stroked ring reads as a drawn
   circle, and the point is that this body has no clean edge. */
.orbit .corona{ fill:var(--sun); opacity:var(--corona-op); }
.orbit .flare { fill:var(--sun); opacity:var(--flare-op); }
/* --orb-ink, not --star. During the dawn the panel is showing the OPPOSITE
   theme while --star still holds the page's own value, so a path keyed to
   --star is drawn in black on the dark panel and simply isn't there until the
   cross-fade lands. Everything inside the figure has to ride the panel's ink. */
.orbit__path { fill:none; stroke:var(--orb-ink); stroke-opacity:.2; stroke-width:1; stroke-dasharray:3 6; }
/* Opacity and width are written per frame from how lit each join is, so nothing
   here sets them — only the colour and the cap. */
/* The panel's own ink, so the one-off dawn can cross-fade every mark in it by
   writing a single property. On the light theme it is a deep violet rather than
   black — a cosmic figure drawn in pure black on white reads as a diagram. */
/* The two ends are the two values --star itself takes: the ink of the page's
   own starfield on the light theme, and on the dark. Written out rather than
   referenced, because the cross-fade has to hold BOTH at once while --star can
   only ever be the current theme's one. */
.orbit{
  --orb-ink-day:   #0B0B10;
  --orb-ink-night: #FFFFFF;
  --orb-bg-day:    #F7F7FB;
  --orb-bg-night:  #12111C;
  --orb-ink: var(--orb-ink-day);
  /* The wordmark's own weight and bloom, kept off the constellation levels so
     the line can be pushed without every star in the panel coming with it. */
  --word-weight:1.8; --word-base:.52; --word-gain:.48;
  --word-glow-base:.14; --word-glow-gain:.45;
}
:root[data-theme="dark"] .orbit{ --orb-ink: var(--orb-ink-night); }
.orbit__veil{
  position:absolute; inset:0; z-index:0;
  border-radius:inherit; pointer-events:none;
}

.orbit__seg  { fill:none; stroke:var(--orb-ink); stroke-linecap:round; }
.orbit__star { fill:var(--orb-ink); }
/* Letters are drawn from segments, not typeset, so they stroke like the
   constellations do. Non-scaling so the weight holds while a glyph scales in. */
.orbit__word path{
  fill:none; stroke:var(--orb-ink);
  stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round;
  vector-effect:non-scaling-stroke;
}
/* The highlight coming off each glyph. */
.orbit__word-glow{ fill:url(#orbSpark); }
/* A constellation join with nothing sitting on it. */
.orbit__thread{ fill:none; stroke:var(--orb-ink); stroke-width:1; stroke-linecap:round; }
/* The product's name in the lower band — same construction, lighter weight, so
   it reads as the caption to the wordmark rather than a second headline. */
.orbit__name-glyph{
  fill:none; stroke:var(--orb-ink);
  stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round;
  vector-effect:non-scaling-stroke;
}
.orbit__trail{ fill:none; stroke:var(--orb-ink); stroke-width:1.2; stroke-linecap:round; }
/* The bloom behind a lit star. A radial gradient rather than a blur filter:
   filters re-rasterise every frame, and there are twenty-two of these. */
.orbit__glow { fill:url(#orbSpark); }
.orbit__ray  { stroke:none; }
/* The body is the moon's NIGHT side, so it is dark on both themes — a pale disc
   in daylight would read as a second sun. Only the limb facing the star is lit,
   which is why the craters and the rim light are white in both. */
.orbit__moon { fill:var(--moon-body); stroke:rgba(255,255,255,.22); stroke-width:1; }
.orbit__crater{ fill:#FFFFFF; opacity:.10; }
/* The lit limb: a sliver of the sun's own colour on the side facing it. */
.orbit__lit  { fill:var(--sun); opacity:.5; }
.orbit__pivot{ fill:var(--orb-ink); opacity:.28; }

/* The star burns harder on the light theme here than it does on the home page.
   The page's own values are tuned for a body floating over white space; inside
   this panel it sits on a bounded card that the sun is meant to dominate, and
   at the page's settings the corona and the filaments wash out to nothing
   against the surface. Scoped to .orbit so the home page keeps its own tuning. */
:root:not([data-theme="dark"]) .orbit{
  /* The body is deliberately softer than the beams it throws. Pushed as hard as
     the filaments it turns into a solid disc and the panel gets a hole burned
     in the middle of it; held back, it stays a light SOURCE and the beams are
     the thing you read. Still above the page's own values (.42/.14/.3). */
  --sun-glow:.58; --corona-op:.26; --flare-op:.44;
  --halo-in:.66; --halo-mid:.24;
  /* The filaments get two levers, not one. --ray-op is the whole beam's
     strength; --ray-far is how far along its own length it survives before
     fading out. On white the tail is the part that disappears first, so
     carrying it further is what actually makes the beams read as reaching the
     constellations rather than stopping just outside the corona. */
  --ray-op:.95; --ray-near:1; --ray-mid:.92; --ray-far:.52;
}
/* Night keeps the page's original tuning — nothing is overridden here on
   purpose, so the star behaves exactly as it did before. */

/* ---------------- narrow ---------------- */
@media (max-width:560px){
  .pdp{ padding-inline:var(--s-4); }
  /* Side by side, not stacked. Three short tiles across a phone still read
     fine once the icon sits beside the label instead of above it. */
  .specs{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:6px; }
  .specs li{ padding:var(--s-3) 6px; }
  .specs svg{ inline-size:17px; block-size:17px; margin-block-end:2px; }
  .specs strong{ font-size:.8125rem; }
  .specs span{ font-size:.6875rem; line-height:1.6; }
}

/* ---------------- reduced motion ----------------
   The gallery keeps a plain cross-fade — the swap still has to be legible, it
   just stops flying. The orbit freezes on a pose (product.js skips its loop). */
@media (prefers-reduced-motion: reduce){
  .aurora__blob{ animation:none; }
  .opt__box, .opt__check, .opt__radio::after, .buybar, .faq__ico::before, .faq__ico::after,
  .frame__chip--tier, .specs svg, .incl__grid svg, #inclTier{ transition:none; }
  .frame__img{ transform:none; filter:none; transition:opacity .25s linear; }
  .frame__img.is-off{ transform:none; filter:none; }
  .frame.is-warping .frame__warp,
  .frame.is-warping .frame__dust i{ animation:none; opacity:0; }
  @media (hover:hover) and (pointer:fine){
    .opt:hover .opt__box{ transform:none; }
  }
}
