{s.title}
{s.body}
diff --git a/GLOSSARY.md b/GLOSSARY.md index c2552ae..a9d3058 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -26,7 +26,19 @@ The public author label of a comment: a snapshot of `users.fullName` taken at su ## الاشتراك قريبًا / Coming soon -The public state of the subscribe flow until Stripe keys exist: `POST /api/subscribe` returns `{ comingSoon: true }` and no `url`; the UI renders a disabled «قريباً» button. No VIP is granted without a Stripe-verified session — neither via the subscribe API nor via `/payment-success` (free users are redirected to `/subscription`). +The public state of the subscribe flow while billing is disabled. From the first paint the UI renders a disabled «قريباً» button (when `LAUNCH_MODE` disables billing there is no active «اشترك الآن» that fails late); if a request is still attempted while Stripe keys are absent, `POST /api/subscribe` returns `{ comingSoon: true }` with no `url`, and the button shows an in-page retry state — **not a redirect** to `/payment-success` and no VIP is minted there either way. No VIP is granted without a Stripe-verified session. + +## الوصول المبكر المجاني / Free early access + +The launch policy (`LAUNCH_MODE=free_early_access`, the compose default): every approved **published** post is readable and its approved files downloadable without an account, regardless of the post's intended tier. Billing stays off; nothing is charged or auto-subscribed. The mode has no date expiry — the ~two-week checkpoint is a review date, not a cutoff. + +## الطبقة المقصودة / Intended tier + +The post's `access` field (`free` | `vip`) — what the content is meant to be once billing is enabled. The launch never rewrites it: a VIP-designated post opened by early access keeps `access: 'vip'` so rollback is instant and cards/comparisons stay truthful. + +## الوصول الفعلي / Effective access + +The decision actually enforced at request time: `status: 'published'` **and** (`access: 'free'` **or** free early access). One policy (`src/lib/launch.ts`) feeds pages, `/api/posts/[slug]`, the file gate, and the llms exports, so visible labels and served bytes can't disagree. A draft is never readable in any mode. ## كتابة موثوقة / Trusted write diff --git a/docs/audits/2026-10-10-marketing-skills-audit.md b/docs/audits/2026-10-10-marketing-skills-audit.md new file mode 100644 index 0000000..4510a0a --- /dev/null +++ b/docs/audits/2026-10-10-marketing-skills-audit.md @@ -0,0 +1,73 @@ +# Marketing-skills audit — anwarpy.ai (2026-10-10) + +> Copy of the local audit of 2026-10-10 (source: agent workspace `marketing-skills-audit-findings.md`), committed for the launch PR. Items marked ❌ were fixed the same day in the launch/early-access branch; see `docs/launch/2026-10-10-launch-readiness.md` for per-item evidence. + +Audited against installed playbooks: paywalls, signup, ai-seo, programmatic-seo, aso (`.agents/skills/`). Evidence: repo `web/src` (file:line) + live production HTML. Verdicts: ✅ compliant · ⚠️ partial · ❌ gap. + +## Paywalls (skill: paywalls v2.0) + +**Current state — technically solid.** Gating = `users.role` (free/vip/admin); VIP body has field-level read ACL (`collections/Posts.ts:57-71`) + page-level select — never served to non-VIP. Teaser paragraphs required for VIP posts (`Posts.ts:39-51`) with fade-out mask; paywall box on locked posts has headline, price-in-CTA («اشترك بـ $10 شهرياً», `lib/format.ts:11`), objection line («تلغي في أي وقت»), login secondary. Per-file byte gate on attachments (`/api/files/[id]`). Stripe: Checkout → `/payment-success` re-verification + webhook activation; deletion webhook downgrades. No dark patterns anywhere. + +**Gaps** +- ❌ Value demonstration: locked H2 titles never shown (`posts/[slug]/page.tsx:202` extracts TOC only when unlocked) — paywall promises «الأقسام العملية» generically. +- ❌ Social proof absent on paywall and subscription (subscription shows guide counts only). No fabricated quotes allowed; needs owner-provided testimonial or real aggregate. +- ❌ No FAQ link or free-escape-hatch inside the paywall box (FAQ lives only on /subscription). +- ❌ Coming-soon discovered after failed click (`subscription/subscribe-button.tsx:16-27`): glossary says disabled «قريباً» button; actual renders active «اشترك الآن» and reveals coming-soon only after POST fails. +- ❌ No measurement (paywall impression/CTA events) — blocked on analytics-tooling owner decision. +- ⚠️ Pricing: monthly $10 only; skill suggests annual toggle — owner policy. +- 🧹 Dead code: `styles/post-locked.css`, `styles/post-unlocked.css` imported nowhere (live styles in globals.css:168-190, design.css:271-287). + +## Signup (skill: signup v2.0) + +**Current state.** 5 fields (الاسم required, email, password ≥8 with show-toggle + hint, تأكيد كلمة المرور, terms checkbox) + hidden `termsVersion`. Native constraint validation with Arabic custom messages; paste allowed; visible labels; single column mobile. Post-submit: auto-login → `/verify-email` (can explore unverified; only commenting gated, 403 with Arabic copy) → `/onboarding` preserving `anwarpi:signup-next`. `safeNext` redirect hygiene. Rate limits: 5/h signup, 10/15min login per-IP (`middleware.ts`), Payload lockout 5/15min (admin exempt, PBKDF2 timing-equalizer). + +**Gaps** +- ❌ **Verification email is never sent at signup** — `sendVerificationEmail` has a single call site (`api/auth/resend-verification/route.ts`); `Users.ts` has no afterChange hook. The verify page claims «أرسلنا رابط التأكيد إلى:» falsely until the user clicks resend. Highest-impact fix. +- ❌ 429 body mis-parsed: signup reads only `err?.errors?.[0]?.message` (`signup/page.tsx:44-47`), so middleware's `{error}` JSON is ignored → rate-limited users see «قد يكون البريد مستخدماً بالفعل» (wrong). Login shows one generic message for all failures (`login/page.tsx:29-31`). Retry-After never surfaced. +- ❌ No spam-folder step and no change-email affordance on `/verify-email` (skill post-submit checklist). +- ❌ Field count above skill's ≤3 guidance (confirm-password cuttable; name kept for comment display names). +- ❌ No «مجاناً — بلا بطاقة ائتمانية» / privacy microcopy near CTA; no inline duplicate-email error with login link; no email-typo suggestion; static (not live) password hint. +- ❌ Social auth absent (Google/Apple) — needs owner (OAuth apps). +- ✅ Already compliant: single email field, no placeholder-only labels, value-story panel, terms timestamped (`termsAcceptedAt`). + +## AI-SEO (skill: ai-seo v2.0) + +**Current state — good SSR baseline.** All pages force-dynamic server components (content in initial HTML); `lang="ar" dir="rtl"`; self-canonical everywhere; title template; sitemap (published posts + lastmod, 8 static paths); robots with disallow list + sitemap pointer; WebSite JSON-LD (home) and Article JSON-LD (posts); answer-first seed content; public per-post JSON at `/api/posts/[slug]`. + +**Gaps** +- ❌ No `llms.txt` / `llms-full.txt`. +- ❌ No named AI-crawler robots policy (implied `*` only); `/api` disallow hides the agent-readable post JSON. +- ❌ Article pages emit **no og:image** — `generateMetadata` (`posts/[slug]/page.tsx:16-33`) defines `openGraph` which *replaces* the root OG object (no siteName/locale/images), though `post.cover.url` exists. +- ❌ Draft leak: the metadata query lacks the `status: 'published'` filter the page query has → draft titles/deks render in metadata of 404 pages. +- ❌ Article JSON-LD missing `image`, `dateModified`, `description`, `mainEntityOfPage`, `publisher`. +- ❌ FAQPage schema absent despite real Q&A (`subscription/page.tsx:16-21` FAQ_ITEMS); BreadcrumbList absent despite breadcrumb UI (`posts/[slug]/page.tsx:179-183`); no Organization schema. +- ❌ Lexical renderer has no `table` node case (~`page.tsx:373-387`) — comparison tables in content fall through to `
`.
+- ⚠️ Freshness: sitemap uses updatedAt but the page shows only publishedAt — add «حُدّث» when they differ.
+- ✅ Answer-first style present; single H1; h2 extraction; agent-accessible free JSON exists.
+
+## Programmatic SEO (skill: programmatic-seo)
+
+**Current state.** Indexable surface = `/`, `/articles`, `/posts/[slug]`, 7 static pages. All category/topic navigation is client-side DOM filtering on ONE URL (`articles-client.tsx:11-19, 62-70`) — clicks never update the URL; `?topic=` states share one canonical. `/catalog` permanently redirects to /articles; `catalog-filters.tsx` + `catalog-deck.tsx` exported nowhere (dead).
+
+**Data available in Postgres** (Posts): title, slug, dek, category (6 values), level (3), access, status, publishedAt/updatedAt, readingMinutes, views, likes, cover, attachments. Public taxonomy already exists: `lib/topics.ts` — 4 topics (`agents` الوكلاء, `models` النماذج, `automation` الأتمتة, `foundations` الأساسيات) mapped from the 6 CMS categories via `topicsFor(post)` + `isTopic()`.
+
+**Decision (owner-confirmed 2026-10-10): build 4 topic hub pages `/topics/[topic]`** — server-rendered directories with unique intros, counts, breadcrumbs + BreadcrumbList, sitemap entries, internal links from home/articles/post breadcrumbs. Later candidates (not now): topic×level personas (gate on ≥3 posts/combo), templates directory, glossary (needs new field). Comparisons/locations/translations: no data, N/A.
+
+## ASO (transferable items only — site, not an app)
+
+- ❌ Home title is brand-only «أنور بآي» — unknown brands need keywords: «أنور بآي — أدلة الذكاء الاصطناعي العملي بالعربية».
+- ❌ Only root + posts have descriptions; give /articles, /subscription, /about their own Arabic descriptions.
+- ❌ One static OG image for everything → per-post `cover` as og:image (same fix as ai-seo list).
+- ❌ Surface «حُدّث» freshness date on articles.
+- ✅ Localization correct (ar locale, RTL, og:locale). Ratings/screenshot fields: N/A.
+
+## Needs owner (not in this pass)
+
+1. Social auth providers (Google/Apple or magic links) — OAuth apps + keys.
+2. Analytics/experimentation tooling (paywall impressions, CTA CTR, form drop-off) — vendor + privacy stance; code ready once chosen.
+3. Annual plan / money-back guarantee — pricing policy.
+4. Testimonial content — must be real; nothing fabricated.
+5. Brand spelling inconsistency: site uses «أنور بآي» everywhere (title, og:site_name, JSON-LD); docs use «أنور بي آي». Pick one canonical spelling.
+6. Policy: allow free users to comment on VIP posts (currently hard-403) — easy conversion lever if allowed.
+7. Sitemap `limit: 500` with no pagination — fine at 13 posts; revisit at scale. /login + /signup kept in sitemap.
+8. `GLOSSARY.md` says free users are *redirected* from /payment-success; actual behavior is an in-page retry state — doc will be aligned to code (no redirect; no VIP minting either way).
diff --git a/docs/launch/2026-10-10-launch-readiness.md b/docs/launch/2026-10-10-launch-readiness.md
new file mode 100644
index 0000000..f1cdb85
--- /dev/null
+++ b/docs/launch/2026-10-10-launch-readiness.md
@@ -0,0 +1,343 @@
+# Anwarpy.ai launch readiness and developer handoff
+
+Prepared for Ahmed and the developer team. Basis: public-site audit on 10 October 2026, refreshed membership/terms/privacy pages, Ahmed's confirmation that Stripe is unfinished, and the installed launch skill's readiness and site-QA framework.
+
+## 1. Decision
+
+**Paid/VIP launch: NO-GO at present. Free early-access launch: CONDITIONAL GO after the launch-blocking checks below pass.**
+
+Stripe does not have to delay a useful free content launch. Launch the complete experience of reading useful Arabic posts and approved downloads first. Run an approximately two-week learning period; treat its end as a review checkpoint, not an automatic paywall date. Continue free early access if Stripe or access control remains unready. Do not advertise an exact paid-start date until approved.
+
+This document is a proposed implementation specification, not evidence that fixes have been applied. No website, Stripe account, source notebook or production database was modified during preparation. Ticket owners are recommended roles; individual assignees and calendar dates remain to be assigned.
+
+### User decisions incorporated
+
+- Blog content originates primarily in AppFlowy and Obsidian notes.
+- An AI agent will help turn approved notes into blog posts.
+- Stripe integration is unfinished.
+- Desired initial period: about two weeks with free access, followed by VIP gating after Stripe is ready.
+
+### Important correction to the earlier audit
+
+Short notes are a legitimate content type. A 150–300-word note need not become a long tutorial to be useful. The problem is a mismatch between short content and labels promising a complete implementation or nine/ten minutes of reading. Add explicit content types and honest estimates. Expand only posts intended to be implementation guides, not every note.
+
+## 2. Evidence and status
+
+Confirmed in the earlier public audit:
+- All 22 unique sitemap URLs returned 200, including 14 posts: eight public full posts and six VIP previews.
+- Public text and membership price were present in initial HTML.
+- Canonicals, Arabic/RTL settings, sitemap and robots.txt exist.
+- Article and WebSite JSON-LD were observed in initial HTML. Schema is not absent.
+- Homepage/library titles are generic; eight non-post pages share a generic description.
+- Login and signup are in the sitemap; no noindex meta was observed in their initial HTML.
+- www serves a 200 homepage while canonical points to the apex domain.
+- First-agent and RAG article bodies were approximately 165 and 154 whitespace-delimited words, despite nine/ten-minute reading estimates.
+- No n8n mention was found in public text of the 14 posts. Paid bodies were not inspected.
+- Some free posts display download controls, while the paid comparison frames working files as a membership benefit. Actual download behavior was not tested.
+
+Rechecked for this handoff:
+- /subscription still advertises $10 monthly, subscription CTAs and Stripe payment.
+- /terms still declares itself a prelaunch draft and calls the price experimental.
+- /privacy still contains prelaunch placeholders for actual providers, retention and effective dates.
+
+Not verified; developer must test:
+- Full reading/download access when launch-free mode is applied.
+- Account signup, email verification, password reset, save/comment flows.
+- Mobile rendering, RTL behavior, browser interactions, accessible controls.
+- Checkout, webhooks, entitlements, cancellation and renewal.
+- Paid-body protection in APIs, page source, hydration payloads, search, feeds, assets and caches.
+- Restore procedures, monitoring, deployed security controls.
+- Search Console index status, field performance, analytics and actual AI citations.
+
+The audit browser failed to start and PageSpeed returned 429. These failures do not establish website defects. Public metadata checks do not certify application behavior.
+
+## 3. Recommended launch policy
+
+### Free launch mode
+
+Recommended initial settings, to adapt to the actual application rather than copy as existing field names:
+
+```yaml
+launch_mode: free_early_access
+billing_enabled: false
+vip_enforcement_enabled: false
+review_checkpoint: approximately_two_weeks_after_actual_launch
+paid_transition: manual_owner_approval_only
+reading_requires_account: false
+newsletter_opt_in_required: true
+```
+
+Open approved published content, including posts designated for future VIP access, during the early-access period. Keep drafts, private source notes, internal comments and unsafe attachments private. Guest reading should work without an account; accounts can remain optional for tested features. If signup is not ready, disable its CTA instead of blocking reading.
+
+**Do not create Stripe trial subscriptions to implement this temporary editorial launch.** A site-level free-access policy is simpler and avoids accidentally collecting cards, issuing charges or making every reader a billing customer.
+
+### Preserve editorial classification
+
+Store the intended tier independently from effective access. A post can be designated VIP for the future while temporarily open now. Recommended conceptual fields:
+
+```yaml
+publication_status: draft | review | published | archived
+content_type: note | tutorial | case_study | comparison
+intended_tier: free | vip
+early_access_eligible: true | false
+source_reference: private_traceability_record
+author_id: approved_real_author
+review_status: pending | approved
+```
+
+The global launch override should apply only to approved published posts and approved associated assets. Never rewrite every VIP post to a permanent free tier, grant permanent VIP user roles, or expose raw CMS records in a public launch feed.
+
+### What happens after the two-week checkpoint?
+
+- If Stripe is not ready: continue free mode, update messaging and set a new review checkpoint.
+- If Stripe is ready but tests fail: continue free mode; repair failures before gating.
+- If tests pass: notify readers, specify the future gated set, have Ahmed approve the transition, then enable billing and gating in a controlled rollout.
+- Posts designated permanently free remain free.
+- Posts explicitly labeled as temporary VIP early-access may become previews. Preserve their URLs and public summaries.
+- Already downloaded public files cannot reliably be recalled. Do not promise otherwise.
+- Readers are never automatically subscribed or charged. Joining an update list is not consent to recurring billing.
+
+Recommended trust-preserving alternative: keep the strongest launch tutorials free permanently and monetize new advanced workflows and files. This is an option for Ahmed to choose, not an assumed change to his requested VIP plan.
+
+## 4. P0 blockers for the free launch
+
+### L-01 — Replace premature paid messaging
+
+**Owner:** developer + Ahmed. **Evidence:** /subscription, homepage membership section and VIP CTAs currently promise purchasing through Stripe.
+
+**Change:** Replace purchase CTAs across home, membership, cards, article gates, menus and account pages. Retain /subscription URL but present early-access status and an optional, functional notification signup. Disable checkout initiation server-side as well as hiding buttons. Never publish a fake working purchase flow.
+
+Suggested Arabic copy:
+
+> وصول مبكر مجاني
+>
+> يمكنك قراءة المحتوى المنشور خلال فترة الوصول المبكر دون بطاقة دفع. نراجع إطلاق العضوية بعد نحو أسبوعين، وقد تمتد الفترة إذا لم تكتمل خدمة الدفع. بعض المواد محددة للعضوية مستقبلاً، وسنوضح ذلك قبل تفعيلها. لن يتم إنشاء اشتراك أو تحصيل مبلغ تلقائياً.
+
+For an intended-VIP post:
+
+> متاح مجاناً خلال الوصول المبكر. هذا المحتوى مخصص للعضوية لاحقاً؛ سنعلن موعد التفعيل قبل تغيير الوصول.
+
+Use only when this is the actual implemented access policy. Do not put an unapproved fixed end date in the UI. Remove '$10 subscribe now' and completed-payment claims until payment is genuinely enabled. A future-price preview, if retained, must be labeled not currently purchasable and subject to owner confirmation.
+
+**Acceptance:** Search all rendered templates and emails for obsolete purchase claims; direct checkout calls fail safely with an explicit unavailable response; no card data is requested; /subscription remains a useful page rather than 404.
+
+### L-02 — Implement consistent launch-free access
+
+**Owner:** backend developer. **Evidence:** six posts currently show public membership previews; desired open period is new policy.
+
+**Change:** One server-side policy function controls article bodies and downloads. It checks publication/review status, actual launch mode, intended tier, and valid entitlement when paid mode is eventually enabled. Frontend labels consume the same effective-access decision.
+
+**Acceptance:** Guest can read every approved launch-open post from its canonical URL, library and related links. Approved downloads work. Draft/private/unreviewed posts remain inaccessible. Original intended tier survives opening and rollback. Repeated config changes are idempotent. Date expiry alone cannot activate a paywall.
+
+### L-03 — Finalize free-launch terms and privacy
+
+**Owner:** Ahmed + qualified reviewer + developer. **Evidence:** /terms and /privacy are publicly marked drafts with unfinished instructions.
+
+**Change:** Document the actual free service, content license, whether future VIP materials are temporary-access, contact channel, real data collection, providers, retention, deletion process and effective date. Distinguish operational email from optional marketing. Remove paid billing provisions that do not apply now or label future features accurately. Do not invent legal entity, governing law, retention duration or compliance claims.
+
+**Acceptance:** Final pages contain facts approved by the operator, no editorial TODO instructions, and a real effective date. Their account and analytics descriptions match the deployed behavior. If analytics requires consent in the applicable context, blocking and withdrawal work. Legal text is professionally reviewed as appropriate; this document is not legal advice.
+
+### L-04 — Test the complete reading and download journey
+
+**Owner:** QA/developer. **Status:** unverified, not a known failure.
+
+**Change:** Test home → library → each launch article → approved file download on desktop and mobile, including Safari and Telegram in-app browser. Check filters, navigation, pagination if any, table/code readability, Arabic text mixed with English commands, and contact links.
+
+**Acceptance:** No placeholder/empty files; names and contents match promises; links and images resolve; keyboard controls and labels work; no horizontal page overflow. Code blocks may scroll independently. For meaningful diagrams use descriptive alt; decorative card covers may have empty alt. Store a browser/version test log and example screenshots.
+
+### L-05 — Protect private notes and publishing permissions
+
+**Owner:** developer + editorial operator. **Status:** new requirement for the publishing pipeline, not an observed leak.
+
+**Change:** Separate public artifacts from AppFlowy/Obsidian sources. Restrict the agent to explicitly approved notes and CMS permissions. Scan text, images and downloads for credentials, private URLs, customer data, internal names and unlicensed assets. Sanitize Markdown/HTML, block executable content and unsafe URLs.
+
+**Acceptance:** Deliberately seeded private markers in test inputs never reach public output. Source notes remain unchanged. An agent cannot publish another author's private note, alter a tier, modify billing, or inject unsafe HTML. Logs contain identifiers and outcomes rather than secrets/full private source text.
+
+### L-06 — Reliable feedback, monitoring and rollback
+
+**Owner:** developer + operator. **Status:** unverified.
+
+**Change:** Ensure at least one monitored support/feedback route works. Add uptime/error visibility, persistence/backups and a tested restore. Test optional signup/reset flows before promoting them; disable unfinished features rather than presenting stubs.
+
+**Acceptance:** A test feedback request reaches the actual destination and confirmation is truthful. A restore/rollback rehearsal recovers public content and configuration. Transactional notices reach the intended test mailbox without leaking data. Operator and emergency contact are named in the runbook.
+
+## 5. P1 work during the early-access period
+
+### L-07 — Content labels and reading estimates
+
+**Owner:** editorial + developer. Add visible 'ملاحظة تقنية', 'دليل عملي', 'تجربة موثقة' or equivalent content-type labels. Compute reading estimate from article body using a configurable rate validated with Arabic readers; exclude navigation/comments and treat exercise time separately. Do not force a minimum long reading time.
+
+**Acceptance:** Short notes are labeled honestly; tutorials show prerequisites and usable steps. The first-agent and RAG labels/estimates match actual content. No arbitrary padding to increase word count.
+
+### L-08 — Metadata and domain hygiene
+
+**Owner:** developer + editorial. Write individual descriptions for home/library/early-access/about. Improve discoverability-oriented titles without adding unsupported topics. Review noindex for login/signup, remove utility pages from sitemap, retain crawl visibility so noindex can be read. Permanently redirect www to apex while preserving path/query. Test nonexisting paths remain 404 and HTTPS canonical consistency.
+
+**Acceptance:** Public posts remain indexable, private/draft pages do not enter sitemap, sitemap URLs return canonical 200s, metadata matches actual article and access status. Never ship staging noindex to public content.
+
+### L-09 — Author, evidence and structured data
+
+**Owner:** editorial + developer. Display real author/reviewer and sources. Enhance Article/BlogPosting fields with author profile, image, dates and canonical identity where correct. Do not invent credentials, reviews or performance data. Validate initial and rendered markup with suitable validators. Preserve existing useful schema.
+
+**Acceptance:** Visible author and schema agree. Update date reflects real substantive updates. Early-access availability and future paywall fields match actual current access; no false paid Offer while billing is off. Validators report understood results; no promise of a special Google AI ranking boost.
+
+### L-10 — Publishing workflow and deduplication
+
+**Owner:** developer + editorial. Implement the companion agent prompt. Map proposed metadata to real CMS fields, create drafts first, review evidence and redactions, approve a specific revision, then publish and read it back. Use stable source IDs plus revision/hash to prevent duplicate posts; existing slugs should not change silently.
+
+**Acceptance:** Same source retried produces no duplicate. Missing-source and failed-test inputs stop safely. Live output is verified against the approved draft. Retries after an ambiguous publish first search/read the expected record. Previous revisions can be restored. No note is marked published until verification succeeds.
+
+### L-11 — Measure the launch
+
+**Owner:** operator + developer. Configure privacy-respecting analytics and search ownership. Suggested events: article_view, meaningful_read, download_success, signup_confirmed, notification_opt_in_confirmed, feedback_received. Fire confirmations on real success, not button clicks. Define meaningful_read rather than using a page-open event as consumption.
+
+**Acceptance:** Test event evidence exists, duplicate analytics installation is avoided, consent is respected, no source notes/emails/credentials appear in event parameters. Verify sitemap and priority URL inspection in Search Console; a search-tool miss is not proof of nonindexation.
+
+### L-12 — Content and launch distribution
+
+**Owner:** Ahmed/editorial. Select an initial set of useful notes and at least one demonstrated implementation if advertised as an implementation library. Publish a specific n8n guide only when an approved working example exists. Keep related links useful; differentiate overlapping beginner posts.
+
+Use the launch skill's owned/rented/borrowed channel mix proportionally:
+- Owned: website and opt-in email updates.
+- Rented: one LinkedIn announcement and a real demonstration where the audience is present.
+- Borrowed: ask relevant collaborators for feedback/share only with permission.
+
+**Acceptance:** Announcement links to an actually accessible post, accurately states free early access, makes no fabricated testimonial or availability claim, and has a monitored feedback path. Product Hunt, affiliate programs and paid ads are optional, not prerequisites for this content launch.
+
+## 6. Stripe/VIP gate — required only before paid launch
+
+Do not block the initial free reading experience on these. Do block purchases and paid enforcement until complete.
+
+### S-01 — Business and account readiness
+
+Operator checks actual Stripe entity eligibility, verification, settlement/payout setup and enabled capabilities for the business jurisdiction. Do not assume an account is eligible because customers are Saudi or a domain is .sa. No workaround through an unsupported entity. Resolve applicable billing/tax requirements with qualified advice. Do not promise a completion timeline based on this document.
+
+### S-02 — Product, price and checkout
+
+Create/verify the approved subscription product and immutable price reference in the correct environment. Confirm currency, monthly interval, actual amount, tax treatment and displayed totals. Use a hosted Checkout subscription flow rather than handling raw card details. Server selects the allowed price, amount and user; reject client-supplied substitutions. Map customer/subscription to the authenticated app user using trusted server-side data.
+
+**Tests:** correct plan, tampered price, wrong customer, repeated checkout, cancelled checkout, authentication-required payment and declined payment. Returning to a success URL never grants VIP by itself. Early free access is not retroactively billed.
+
+### S-03 — Verified webhook processing
+
+Use official signature verification with the raw body and the proper environment-specific signing secret. Persist event IDs, process duplicates safely and reconcile subscription state instead of assuming delivery order. Durably record/enqueue work before acknowledging; maintain retry/dead-letter visibility. Use narrowly selected relevant events such as checkout completion, invoice paid/payment failure and subscription changes/end as required by the chosen integration; account for delayed payment methods if enabled.
+
+**Tests:** invalid signature, duplicate delivery, out-of-order event, network interruption, delayed event, update/cancellation and recovery. A duplicate event neither duplicates entitlements nor creates additional charges. Test mode never activates production VIP rights.
+
+### S-04 — Server-side entitlements and lifecycle policy
+
+Define explicit access rules for active, incomplete, unpaid, past-due, canceled and any genuinely supported trial states. Document grace periods; do not invent one silently. Maintain paid-through timestamps and cancellation-at-period-end behavior consistent with promises. Reconcile against Stripe after missed events; do not revoke valid existing access merely because of a transient retrieval error. New unverifiable purchases stay pending and are not granted based on UI redirects.
+
+**Tests:** paid user receives correct access; nonpayer cannot gain VIP by changing frontend storage; scheduled cancellation retains access through paid end and expires correctly; renewal extends once; failed renewal follows the approved policy; reversal/refund/dispute handling matches business policy.
+
+### S-05 — Prevent full-content leaks
+
+Paid gating must exclude restricted bodies/assets for unauthorized users from server HTML, page hydration/RSC payloads, APIs, GraphQL/REST responses, RSS/search feeds, public files, alternate Markdown and optional llms exports. Do not send full bodies and blur/hide them with CSS. Protect VIP downloads server-side and use scoped delivery where needed.
+
+Separate guest/free/member caching; invalidate public copies of newly restricted content when switching. Previously public copies on third-party caches cannot be fully recalled.
+
+**Tests:** guest/free user sees a useful public intro but never the restricted payload; valid member can read/download; expired member is denied; direct asset/API attempts do not bypass checks. If a crawler sampling policy is used, follow applicable search guidance without returning special promotional content or disabling authentication for arbitrary bot strings.
+
+### S-06 — Customer self-service and notices
+
+Provide a tested billing-management path for invoices, payment-method updates and cancellation. The app must generate a portal session for the logged-in user's own customer, not arbitrary IDs. Verify actual supported portal language; do not promise Arabic availability without testing. Keep Arabic app guidance around an English portal if needed. Confirm receipt/support contacts, statement details, renewal/cancellation notices and refund policy.
+
+### S-07 — Sandbox acceptance and production preparation
+
+Use Stripe sandbox/test resources and official test methods for success, failures, authentication, renewals and time-based lifecycle scenarios. Do not use real card data or arbitrary real purchases for testing. Record environment, SDK/API versions, relevant object/event IDs, expected result, actual result and pass/fail, with secrets removed. Test signing-secret rotation and failure visibility. Production keys and price IDs are stored securely and never inserted into source notes, prompts or repositories.
+
+### S-08 — Owner-approved transition and rollback
+
+Before switch, take an access/content snapshot, confirm notification copy and approve a list of specific intended-VIP posts/assets. Keep canonical URLs; retain public introductions and meaningful metadata. Synchronize all counters, cards, comparison tables and policies. Invalidate relevant caches and verify guest/member journeys immediately.
+
+Rollback should disable new purchases and revert the faulty restriction configuration without deleting Stripe records, canceling paid customers or erasing paid entitlements. Repair paid readers' access quickly and reconcile outstanding events. A kill switch is not authorization to give all private content away.
+
+## 7. Approximate two-week operating plan
+
+Relative timing only; actual launch date is not yet set.
+
+- Before day 1: close L-01 through L-06, approve content/access labels, and complete the free-launch QA gate.
+- First few days: publish one useful launch post, invite a small set of readers, gather specific feedback, fix broken journeys before wider announcement.
+- First week: ship reviewed notes and at least one evidence-backed implementation if available; correct metadata/reading estimates; test notifications and source pipeline.
+- Second week: review read/download/feedback evidence, publish improvements, and complete Stripe sandbox work in parallel.
+- Around the two-week checkpoint: inspect the free experience and Stripe/VIP test matrix. Either extend early access honestly or schedule an owner-approved paid transition. Do not activate gating solely because a calendar timer expired.
+
+No automatic schedule or campaign was created by this document.
+
+## 8. Developer signoff checklist
+
+### Free launch GO only when
+
+- [ ] L-01: paid claims and backend purchase paths are disabled consistently.
+- [ ] L-02: all approved launch-open content/assets work for intended readers; private content stays private.
+- [ ] L-03: real free-launch terms/privacy/access expectations are approved and published.
+- [ ] L-04: desktop/mobile/in-app reading and download QA passes.
+- [ ] L-05: note redaction, safe rendering and agent permissions pass negative tests.
+- [ ] L-06: feedback, monitoring, backup/restore and rollback work.
+- [ ] Ahmed approves the precise launch-open set and announcement.
+
+### Paid launch GO only when
+
+- [ ] S-01 to S-08 pass with recorded evidence.
+- [ ] Actual billing currency, amount, tax and refund handling are approved.
+- [ ] Guest, free, active member and expired member access matrices pass.
+- [ ] No restricted data appears in anonymous responses/feeds/assets/caches.
+- [ ] Readers receive honest notice of the transition and no automatic charge.
+- [ ] Ahmed signs off the transition; operator and rollback owner are assigned.
+
+For each item record: ticket ID; assignee; environment; PR/deployment ID; test steps; expected/actual result; evidence link; status; remaining risk; reviewer. A checkbox without evidence is not release verification.
+
+## 9. Decisions Ahmed/developer must settle
+
+1. Which existing intended-VIP posts/assets are temporarily opened? Recommended: approved published VIP materials only, not raw notes or drafts.
+2. Which launch tutorials remain permanently free? Recommended: retain core introductory tutorials for discovery.
+3. What must require an account? Recommended: guest reading; account only for tested save/comment functions.
+4. Is an email-update feature ready? If not, omit the CTA rather than deploying a fake signup.
+5. What notice period will be used before restricting temporary VIP early-access materials? Agree and implement; no arbitrary period is treated as approved here.
+6. Who approves posts and launch switches? Until explicitly delegated, human approval is required for a specific article revision and for paid-mode changes.
+7. What are the actual retention periods, providers, legal entity and billing jurisdiction? Operator supplies facts; the agent must not invent them.
+
+## 10. Reference links
+
+Website evidence:
+- https://anwarpy.ai/
+- https://anwarpy.ai/articles
+- https://anwarpy.ai/subscription
+- https://anwarpy.ai/terms
+- https://anwarpy.ai/privacy
+- https://anwarpy.ai/posts/first-arabic-agent
+- https://anwarpy.ai/posts/simple-arabic-rag
+- https://anwarpy.ai/posts/dograh-voice-agent-evaluation
+- https://anwarpy.ai/sitemap.xml
+- https://anwarpy.ai/robots.txt
+
+Stripe primary guidance consulted:
+- https://docs.stripe.com/billing/subscriptions/webhooks
+- https://docs.stripe.com/webhooks
+- https://docs.stripe.com/testing
+- https://docs.stripe.com/customer-management
+
+Companion document: anwarpy-blog-publishing-agent-prompt.md. The prompt controls content generation and approval, not billing activation.
+
+## ملخص عربي
+
+الإطلاق المدفوع غير جاهز الآن لأن ربط Stripe لم يكتمل. يمكن إطلاق قراءة مجانية مبكرة بعد إصلاح رسائل الاشتراك والسياسات، وفتح المواد المعتمدة فقط، واختبار القراءة والتنزيل وحماية الملاحظات الخاصة. الأسبوعان نقطة مراجعة وليسا موعد حجب تلقائي. الملاحظات القصيرة مقبولة إذا سُمّيت ملاحظات وكان وقت القراءة صادقاً. لا تتحول المواد إلى VIP إلا بعد نجاح اختبارات الدفع والصلاحيات وإبلاغ القراء وموافقة أحمد، ولا يُنشأ اشتراك أو يُحصّل مبلغ تلقائياً.
+
+---
+
+## 11. Evidence — free-launch items executed 2026-10-10
+
+Launch implemented on branch `launch/early-access-2026-10-10` (step references = execution plan). `LAUNCH_MODE=free_early_access` is the compose default; billing stays off until owner approval. Per the checklist rule, a checkbox without evidence is not verification — prod-verify items below are re-checked at step 48 and the lines updated.
+
+- **L-01 (paid claims off)** — Steps 8–11, 21, 22: `/subscription` shows an early-access panel + working notify form; all subscribe CTAs render a disabled «قريباً» button when billing is off (`subscribe-button.tsx` comingSoon prop); `POST /api/subscribe` returns 503 server-side with the exact Arabic error; home membership copy swept for purchase claims. *Prod re-check: step 48.*
+- **L-02 (launch-free access policy)** — Steps 1–6: `src/lib/launch.ts` policy (`LAUNCH_MODE`, fail-safe default = free early access, no date expiry), field-level `content` read ACL in `Posts.ts` (public iff `status=published && (access=free || early)`) — same decision consumed by pages, `/api/posts/[slug]`, file gate, and llms-full. Intended tier (`access`) never rewritten. *Verified: migration chain + step 45/48 ghost-read.*
+- **L-03 (terms/privacy)** — **NOT DONE — owner facts pending.** `docs/launch/owner-inputs-terms-privacy.md` lists exactly what Ahmed must supply; `/terms` and `/privacy` untouched until then.
+- **L-04 (reading/download QA)** — Step 45 local smoke + step 48 prod QA (desktop 1280px, mobile 390px Chromium, home → library → topic hub → article → download). Safari and Telegram in-app browsers are owner-manual tests.
+- **L-05 (private notes / pipeline permissions)** — Steps 28–30: agent API strips `access`/`readingMinutes` from accepted fields; `sourceId` dedup returns the existing post (no duplicate); `scanForSecrets` 422s payloads matching AWS AKIA / PEM / sk- / ghp_ / generic-credential patterns (pattern names only in the response); publish stays an explicit opt-in and PUT cannot set `status`.
+- **L-06 (feedback, backup, rollback)** — Step 40: `scripts/backup-db.sh` + restore rehearsal on the VPS (2026-10-10): `pg_dump` → gzip 25K, restored into `anwarpi_restore_test` with 0 SQL errors, counts `posts=15, users=6, payload_migrations=6`, temp DB dropped. Feedback: footer `mailto:hello@anwarpy.ai` (opens the visitor's mail client; no server dependency). Production mail transport test via the same Cloudflare Email Sending REST call the app uses → `10203 email.sending_disabled` — **the account feature is off; transactional mail (verification, notify confirmations) will honestly report failure until the owner enables Email Sending**. Monitoring/uptime tooling: owner decision, recorded not built.
+- **L-07 (labels + reading estimates)** — Steps 24–26: `contentType` (note/tutorial/case_study/comparison) badge, `authorName` byline, «حُدّث» freshness line; `readingMinutes` computed at write time via `countLexicalWords`/`READING_WORDS_PER_MINUTE` (160 wpm) with `scripts/backfill-reading-minutes.ts` (13 scanned, 0 drift on seeded content).
+- **L-08 (metadata/domain hygiene)** — Steps 32, 37, 38, 39: Arabic descriptions on articles/subscription/about; sitemap gains `/topics/*` and drops `/login`,`/signup`; those pages (plus forgot-password) are `robots: noindex, follow`; Traefik `anwarpi-www` router 301s `https://www.anwarpy.ai/*` → apex with priority above the API router. *Prod re-check: `curl -sI https://www.anwarpy.ai/posts`, step 48.*
+- **L-09 (author/evidence/structured data)** — Step 33: Article JSON-LD with `image`, `dateModified`, `description`, `mainEntityOfPage`, publisher logo, `author` = Person (authorName) or Organization; `isAccessibleForFree` = actual current access (never a false paid Offer while billing is off); BreadcrumbList (home → topic hub → post); FAQPage on `/subscription`; Organization on home. Visible byline and schema share the same fields.
+- **L-10 (publishing workflow/dedup)** — Steps 24, 28–31: agent creates drafts (`status` forced draft unless `publish: true`), `sourceId` unique-ish dedup before slug-409, secret scan on POST/PUT.
+- **L-11 (measurement)** — **NOT BUILT — owner decision** (analytics vendor; recorded only). Suggested events remain in the checklist.
+- **L-12 (content/distribution)** — **Owner/editorial.** No announcement drafted or sent by the agent.
diff --git a/docs/launch/owner-inputs-terms-privacy.md b/docs/launch/owner-inputs-terms-privacy.md
new file mode 100644
index 0000000..67179a3
--- /dev/null
+++ b/docs/launch/owner-inputs-terms-privacy.md
@@ -0,0 +1,27 @@
+# Owner inputs required for L-03 — /terms and /privacy
+
+Ahmed must supply the facts below before `/terms` or `/privacy` are edited. The agent will not invent any of them (checklist §9.7). Once provided, the pages get real text, an effective date, and go into the launch checklist evidence.
+
+## Legal facts (checklist L-03 + §9)
+
+1. **Legal entity** — registered company name and form, or an explicit statement that the site is operated as a sole proprietorship (and under what name).
+2. **Governing law / jurisdiction** — the law that governs the terms and the courts or venue for disputes.
+3. **Retention periods per data type** — accounts and hashed passwords, verification/reset tokens, comments, saved posts, launch-notification emails, server logs. Include "until account deletion" where that is the real answer.
+4. **Full provider list** — as currently known: Cloudflare (DNS/proxy, Email Routing + Email Sending), VPS host for 147.79.102.201, Postgres (self-hosted on that VPS). Confirm or correct; note anything that processes personal data outside these.
+5. **Data-deletion process** — how a user requests deletion (email address or in-app), what is deleted vs retained, and the timeframe.
+6. **Contact channel** — the address that appears in the pages and must actually reach Ahmed (currently the footer `mailto:hello@anwarpy.ai`).
+7. **Effective date** — the date the final terms/privacy go live (this becomes the published "آخر تحديث").
+
+## Checklist §9 decisions (needed to finalize the same pages)
+
+1. Which existing intended-VIP posts/assets are temporarily opened? (Recommended: approved published VIP materials only.)
+2. Which launch tutorials remain permanently free? (Recommended: core introductory tutorials.)
+3. What requires an account? (Recommended: guest reading; account only for save/comment.)
+4. Is an email-update feature ready? (The notify list exists; confirm it may be advertised.)
+5. Notice period before restricting temporary VIP early-access materials.
+6. Who approves posts and launch switches (Ahmed until delegated)?
+7. Actual retention periods, providers, legal entity and billing jurisdiction (see items 1–7 above).
+
+## Operational blocker found 2026-10-10 (also affects the pages' email claims)
+
+Cloudflare Email Sending is **disabled** on the account (API test returned `10203 email.sending_disabled`). Before any page or flow promises email delivery: enable Email Sending in the Cloudflare dashboard (Account → Email), then re-test the transport. The footer mailto link is unaffected (it opens the visitor's own mail client).
diff --git a/web/docker-compose.vps.yml b/web/docker-compose.vps.yml
index 7830e04..f165381 100644
--- a/web/docker-compose.vps.yml
+++ b/web/docker-compose.vps.yml
@@ -30,17 +30,29 @@ services:
PAYLOAD_SECRET: ${PAYLOAD_SECRET}
AGENT_API_KEY: ${AGENT_API_KEY}
NEXT_PUBLIC_SITE_URL: https://anwarpy.ai
+ LAUNCH_MODE: ${LAUNCH_MODE:-free_early_access}
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID}
MAIL_FROM: noreply@anwarpy.ai
labels:
- traefik.enable=true
- - traefik.http.routers.anwarpi.rule=Host(`anwarpy.ai`) || Host(`www.anwarpy.ai`)
+ - traefik.http.routers.anwarpi.rule=Host(`anwarpy.ai`)
- traefik.http.routers.anwarpi.entrypoints=websecure
- traefik.http.routers.anwarpi.tls.certresolver=mytlschallenge
- traefik.http.routers.anwarpi.middlewares=pagelimit@docker,connlimit@docker
- traefik.http.services.anwarpi.loadbalancer.server.port=3000
- - traefik.http.routers.anwarpi-api.rule=PathPrefix(`/api`) && (Host(`anwarpy.ai`) || Host(`www.anwarpy.ai`))
+ # 39: canonical host is the apex. www hits this router first (priority
+ # above anwarpi-api's 100) and is 301-redirected before any rate limit.
+ - traefik.http.routers.anwarpi-www.rule=Host(`www.anwarpy.ai`)
+ - traefik.http.routers.anwarpi-www.entrypoints=websecure
+ - traefik.http.routers.anwarpi-www.tls.certresolver=mytlschallenge
+ - traefik.http.routers.anwarpi-www.priority=110
+ - traefik.http.routers.anwarpi-www.service=anwarpi
+ - traefik.http.routers.anwarpi-www.middlewares=www-redirect
+ - traefik.http.middlewares.www-redirect.redirectregex.regex=^https://www\.anwarpy\.ai/(.*)
+ - traefik.http.middlewares.www-redirect.redirectregex.replacement=https://anwarpy.ai/$$1
+ - traefik.http.middlewares.www-redirect.redirectregex.permanent=true
+ - traefik.http.routers.anwarpi-api.rule=PathPrefix(`/api`) && Host(`anwarpy.ai`)
- traefik.http.routers.anwarpi-api.entrypoints=websecure
- traefik.http.routers.anwarpi-api.tls=true
- traefik.http.routers.anwarpi-api.tls.certresolver=mytlschallenge
diff --git a/web/scripts/backfill-reading-minutes.ts b/web/scripts/backfill-reading-minutes.ts
new file mode 100644
index 0000000..a89563a
--- /dev/null
+++ b/web/scripts/backfill-reading-minutes.ts
@@ -0,0 +1,40 @@
+/**
+ * Backfill `readingMinutes` on every post from its body (L-07).
+ * No schema change — safe to re-run any time.
+ *
+ * Usage: cd web && npx tsx scripts/backfill-reading-minutes.ts
+ */
+import { getPayload } from 'payload'
+import configPromise from '@payload-config'
+import { countLexicalWords } from '../src/lib/lexical'
+import { READING_WORDS_PER_MINUTE } from '../src/lib/launch'
+
+const payload = await getPayload({ config: configPromise })
+
+const { docs } = await payload.find({
+ collection: 'posts',
+ limit: 500,
+ depth: 0,
+ overrideAccess: true,
+})
+
+let updated = 0
+for (const post of docs) {
+ const words = countLexicalWords(post.content)
+ const readingMinutes = Math.max(1, Math.round(words / READING_WORDS_PER_MINUTE))
+ if (readingMinutes !== post.readingMinutes) {
+ await payload.update({
+ collection: 'posts',
+ id: post.id,
+ data: { readingMinutes },
+ overrideAccess: true,
+ context: { systemBackfill: true },
+ })
+ payload.logger.info(`backfill: ${post.slug} ${post.readingMinutes} → ${readingMinutes} (${words} words)`)
+ updated += 1
+ }
+}
+
+payload.logger.info(`backfill complete: ${docs.length} scanned, ${updated} updated`)
+// Postgres pool keeps the loop alive — release it so the script exits.
+await payload.db.destroy?.()
diff --git a/web/scripts/backup-db.sh b/web/scripts/backup-db.sh
new file mode 100755
index 0000000..5457a58
--- /dev/null
+++ b/web/scripts/backup-db.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+# anwarpy.ai — Postgres backup (L-06)
+#
+# Run ON THE VPS (needs the `proxy`/compose network up):
+# ./scripts/backup-db.sh
+#
+# Cron (nightly 03:15, keep dir writable):
+# 15 3 * * * cd /root/anwarpi && ./scripts/backup-db.sh >> backups/backup.log 2>&1
+#
+# Restore a dump into the running db container:
+# gunzip -c backups/anwarpi-YYYYMMDD-HHMMSS.sql.gz | \
+# docker compose -f docker-compose.vps.yml exec -T db psql -U anwarpi anwarpi
+#
+# Restore-rehearsal (verify a backup is loadable BEFORE you need it):
+# gunzip -c backups/ {guidesLabel(posts.length)}
{post.category}
+ {contentTypeLabel(post.contentType) && {contentTypeLabel(post.contentType)}}
{minutesLabel(post.readingMinutes)}
{post.level}
{meta._match && يناسب مستواك}
diff --git a/web/src/app/(frontend)/catalog/catalog-deck.tsx b/web/src/app/(frontend)/catalog/catalog-deck.tsx
deleted file mode 100644
index 288f40c..0000000
--- a/web/src/app/(frontend)/catalog/catalog-deck.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-'use client'
-
-import { useEffect, useState } from 'react'
-import Link from 'next/link'
-
-export type DeckSlide = {
- kicker: string
- index: string
- title: React.ReactNode
- body: string
- format: React.ReactNode
- link?: { href: string; label: string }
- meta?: React.ReactNode
-}
-
-const V1_SLIDES: DeckSlide[] = [
- {
- kicker: 'دليل حصري · المستوى المتوسط',
- index: '01',
- title: 'ابنِ وكيلك الأول دون فوضى',
- body: 'خريطة عملية لتقسيم المهام، اختيار الأدوات، وضبط ذاكرة الوكيل قبل كتابة الكود.',
- format: <>فيديو · 28 دقيقة>,
- link: { href: '/posts/agent-loop-stop-condition', label: 'شاهد المعاينة ←' },
- },
- {
- kicker: 'مقال تطبيقي · للمبتدئين',
- index: '02',
- title: 'كيف تختار نموذجاً لمهمتك؟',
- body: 'مقارنة منهجية حسب الجودة، التكلفة، السرعة، والخصوصية — من دون ضجيج تسويقي.',
- format: <>قراءة · 12 دقيقة>,
- link: { href: '/posts/article-agent-guide', label: 'ابدأ القراءة ←' },
- },
- {
- kicker: 'مسار متقدم · للأعضاء',
- index: '03',
- title: <>من RAG إلى إجابة موثوقة>,
- body: 'صمّم الاسترجاع، اختبر المصادر، وافهم أين تنهار الإجابة قبل أن تصل إلى المستخدم.',
- format: <>سلسلة · 4 أجزاء>,
- link: { href: '/subscription', label: 'شاهد للأعضاء ←' },
- },
-]
-
-const DOT_LABELS = ['الشريحة الأولى', 'الشريحة الثانية', 'الشريحة الثالثة']
-
-/* Featured content carousel. Manual selection pauses rotation until resumed. */
-export default function CatalogDeck({ slides = V1_SLIDES }: { slides?: DeckSlide[] }) {
- const [activeSlide, setActiveSlide] = useState(0)
- const [ready, setReady] = useState(false)
- const [playing, setPlaying] = useState(true)
- const [hovered, setHovered] = useState(false)
- const [focused, setFocused] = useState(false)
- const current = slides.length ? activeSlide % slides.length : 0
- const advancing = ready && playing && !hovered && !focused && slides.length > 1
-
- useEffect(() => {
- try {
- const stored = Number(localStorage.getItem('midad-slide'))
- if (Number.isInteger(stored) && stored >= 0) setActiveSlide(stored)
- } catch { /* Persistence is optional. */ }
- const motion = window.matchMedia('(prefers-reduced-motion: reduce)')
- if (motion.matches) setPlaying(false)
- const onMotion = () => { if (motion.matches) setPlaying(false) }
- motion.addEventListener('change', onMotion)
- setReady(true)
- return () => motion.removeEventListener('change', onMotion)
- }, [])
-
- useEffect(() => {
- if (!ready) return
- try { localStorage.setItem('midad-slide', String(current)) } catch { /* optional persistence */ }
- }, [current, ready])
-
- useEffect(() => {
- if (!advancing) return
- const timer = setTimeout(() => setActiveSlide(index => (index + 1) % slides.length), 6500)
- return () => clearTimeout(timer)
- }, [advancing, current, slides.length])
-
- function selectSlide(index: number) {
- setActiveSlide((index + slides.length) % slides.length)
- setPlaying(false)
- }
-
- if (!slides.length) return null
-
- return (
- {s.body} الأدلة المجانية كاملة دائماً. العضوية تضيف الأدلة المعمّقة، القوالب الجاهزة للتنزيل، والنقاش مع الفريق. {MEMBERSHIP_PRICE} شهرياً، وتلغي متى شئت وصول مبكر مجاني يمكنك قراءة المحتوى المنشور خلال الوصول المبكر دون بطاقة دفع. {MEMBERSHIP_PRICE} شهرياً، وتلغي متى شئت متاح مجاناً خلال الوصول المبكر. هذا المحتوى مخصص للعضوية لاحقاً؛ سنعلن موعد التفعيل قبل تغيير الوصول. قرأت المقدمة المجانية. العضوية تفتح بقية الأقسام العملية{hasAgentFiles ? '، وقالب حالة الوكيل وقائمة الفحص قبل التشغيل' : ''}، وكل الأدلة الحصرية الأخرى. ماذا ينتظرك داخل هذا الدليل: تلغي في أي وقت من صفحة حسابك. تلغي في أي وقت من صفحة حسابك. أسئلة قبل الاشتراك · أكمل بالأدلة المجانية {inner} {inner} أدخل بياناتك الأساسية للبدء. هذا البريد مسجَّل لدينا. تسجيل الدخول مجاناً — بلا بطاقة ائتمانية. لن نشارك بريدك أبداً. لديك حساب بالفعل؟ سجّل الدخول {done} يمكنك قراءة المحتوى المنشور خلال فترة الوصول المبكر دون بطاقة دفع. نراجع إطلاق العضوية بعد نحو أسبوعين، وقد تمتد الفترة إذا لم تكتمل خدمة الدفع. بعض المواد محددة للعضوية مستقبلاً، وسنوضح ذلك قبل تفعيلها. لن يتم إنشاء اشتراك أو تحصيل مبلغ تلقائياً. {MEMBERSHIP_PRICE}شهرياً تلغي متى شئت، ويبقى حسابك المجاني ومحفوظاتك. سعر مستقبلي — غير قابل للشراء حالياً. تلغي متى شئت، ويبقى حسابك المجاني ومحفوظاتك. الدفع عبر Stripe. لا نحفظ بيانات بطاقتك. {early ? 'لن يتم إنشاء اشتراك أو تحصيل مبلغ تلقائياً.' : 'الدفع عبر Stripe. لا نحفظ بيانات بطاقتك.'}
-
+ {billingEnabled
+ ?
+ {s.title}
{TOPICS.map(topic => (
بقية هذا الدليل للأعضاء
{toc.map((t, i) =>
+
case 'horizontalrule': return {n.code ?? innerText(n.children)}
+ case 'table':
+ return (
+ {inner}
+ {inner}
+ case 'tablecell': return n.headerState ? {inner} : {inner}
default: return إنشاء حساب
وصول مبكر مجاني
+ عضوية فردية