From 3fcbca8f8e6a3e23a2da85ea3c9785e8295a34cf Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Thu, 20 Aug 2026 17:40:08 +0330 Subject: [PATCH] docs(design): define typography and dark theme --- DESIGN.md | 51 +++++++++++++++++++++++++++++++++--------- docs/DASHBOARD-SPEC.md | 8 ++++++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index b852e88..7fb7565 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,7 +1,7 @@ # Design System — ZarinPal Merchant Analytics -- Status: Draft baseline for approval -- Version: 0.2.4 +- Status: Approved baseline — licensed font assets pending; implementation not yet authorized +- Version: 0.3.0 - Last updated: 2026-08-20 - Direction: Persian-first RTL @@ -34,10 +34,39 @@ The authenticated panel screenshots are the primary behavioral reference: fixed All text/background pairs must meet WCAG 2.2 AA contrast. Chart palettes require patterns, labels, or shapes in addition to hue. +### Theme contract + +Light and dark modes are first-class variants of the same semantic token system. Components consume role tokens rather than raw palette values; dark mode is not a color inversion or an after-launch skin. + +| Semantic role | Light | Dark | Notes | +|---|---:|---:|---| +| `--color-canvas` | `#F1F1F3` | `#0F0F10` | Application workspace | +| `--color-surface` | `#FFFFFF` | `#19191A` | Cards, drawers, dialogs | +| `--color-surface-muted` | `#F7F7F8` | `#28282A` | Nested panels and table headers | +| `--color-text-primary` | `#19191A` | `#FFFFFF` | Primary content | +| `--color-text-secondary` | `#3D3D42` | `#D1D1D6` | Supporting content | +| `--color-text-muted` | `#6B6B73` | `#A7A7AF` | Metadata; contrast must be verified at actual size | +| `--color-border` | `#E3E3E7` | `#3A3A3D` | Dividers and neutral outlines | +| `--color-brand-action` | `#0A33FF` | `#90AEF4` | Primary action and active state | +| `--color-on-brand` | `#FFFFFF` | `#0A0A0A` | Content on brand action | +| `--color-focus-ring` | `#0A33FF` | `#90AEF4` | Keyboard focus | +| `--color-semantic-success` | `#248061` | `#6FD6AE` | Positive values with text/icon support | +| `--color-semantic-warning` | `#8A5300` | `#F3BE5B` | Caution and incomplete verification | +| `--color-semantic-danger` | `#C93637` | `#FF8A8B` | Error and destructive meaning | +| `--color-semantic-info` | `#0A33FF` | `#90AEF4` | Informational emphasis | + +- The initial theme follows `prefers-color-scheme` unless the user has explicitly selected light or dark. The explicit choice is persisted locally because it is presentation-only and non-sensitive. +- Theme selection is applied before first paint to avoid a light/dark flash. The toggle remains keyboard and screen-reader operable and exposes its current state. +- Semantic success, warning, danger, information, chart, hover, pressed, disabled, skeleton, scrim, and elevation tokens have separately tested light/dark values before delivery. +- Every data series maintains at least 3:1 non-text contrast against its chart surface. Text maintains WCAG 2.2 AA, and status/trend meaning never depends on color alone. + ### Typography -- Preferred Persian UI family: a legally distributable Yekan-family webfont if supplied/licensed for this project. -- Safe open fallback: `Vazirmatn`, then `Tahoma`, system sans-serif. +- Required Persian UI family: licensed **Iran Yekan X Professional** (`IRANYekanX`), matching the current ZarinPal panel's `IRANYekanXVFaNumVF` variable font. +- Required asset: the Persian-number (`FaNum`) variable webfont in WOFF2, covering the `wght` axis from 100–1000. The product uses 400 for body/value text, 500 for labels and controls, 600 for emphasis, and 700 for section/page headings. No separate static weights are required when the variable file is supplied. +- Purchase/issue the website or software license for this project and domain. Font files are proprietary assets: they must be delivered privately, self-hosted by the frontend, excluded from public-source distribution unless the license explicitly permits it, and never copied from ZarinPal's CDN. +- Runtime fallback while the licensed asset is unavailable: `Tahoma`, system sans-serif. The fallback is for resilience, not the approved visual baseline. +- Evidence: inspection of the current [ZarinPal panel](https://next.zarinpal.com/beta/panel) found `IRANYekanXVFaNumVF` declared for weights 100–1000. The licensed [Iran Yekan X product page](https://fontiran.com/fonts/iranyekan) identifies the professional package as the option containing all weights, the variable font, Persian-number assets, and WOFF/WOFF2 webfonts. - Use tabular numerals for tables/KPIs where supported. - Body: 14–16 px, line-height 1.7–1.9 for Persian. - Labels/meta: 12–14 px; never below 12 px. @@ -94,6 +123,7 @@ Navigation labels should describe merchant tasks, not internal database concepts ## Core component contracts - **App shell:** sidebar/drawer, utility header, page title, merchant identity, analytics progress/status. First access idempotently starts a run, then opens its authenticated SSE stream; the page reveals overview, payment performance, fees, and peer comparison independently as each becomes ready. +- **Theme control:** accessible light/dark toggle in the utility area, initialized from the persisted choice or system preference and applied without a first-paint flash. - **Metric card:** label, formatted value/unit, comparison and period, semantic trend, definition link, independent queued/calculating/ready/failed states. - **Chart card:** title, period/filter controls, concise takeaway, plot, legend, accessible tabular alternative, provenance link, and section-local loading/error behavior. - **Insight card:** deterministic finding, numeric evidence, suggested merchant action, confidence/limitation; AI-generated copy is visibly labeled later. @@ -112,6 +142,7 @@ Navigation labels should describe merchant tasks, not internal database concepts - Tooltips are keyboard reachable and not the sole location of important values. - Charts have a textual summary and downloadable/visible data table when practical. - Zero, unavailable, suppressed, and not-yet-calculated are visually and linguistically distinct. +- A deterministic insight card appears only with at least 100 relevant sessions and a qualifying change: at least 3 percentage points for rates or 20 percent for amounts/counts. Smaller comparisons remain visible in metrics without being promoted into an action claim. - A ready section appears immediately without layout-shifting or blocking on unfinished siblings. Overall progress remains understandable without relying on animation. - Source statuses `Paid` and `Verified` both count as successful bank payments because funds were deducted. Keep them visually distinguishable: `Paid` means merchant verification is incomplete and is an actionable gap; `Verified` is finalized. - Present `fee_amount_rial` as the merchant's real fee, summed once per session. Never multiply it by retry attempts or label it as obfuscated/relative-only in the product. @@ -131,7 +162,9 @@ Navigation labels should describe merchant tasks, not internal database concepts - Root UI is `lang="fa" dir="rtl"`. - Technical identifiers, URLs, hashes, and mixed Latin values use isolated LTR spans (`dir="ltr"`) to prevent bidi corruption. -- Use `Intl.NumberFormat`/`Intl.DateTimeFormat`. Display merchant-facing dates in the Jalali calendar and `Asia/Tehran`; API instants remain ISO-8601 UTC. Specifications must still choose Persian vs Latin digits and Rial/Toman terminology before implementation. +- Merchant-facing numbers, percentages, chart axes, counts, dates, and monetary values use Persian digits through locale-aware formatting (`fa-IR`). Technical identifiers and machine-facing values retain their canonical Latin representation inside isolated LTR spans. +- Display money in **Rial only**, with an explicit `ریال` unit. Do not silently convert to Toman or mix the two units. +- Use `Intl.NumberFormat`/`Intl.DateTimeFormat`. Display merchant-facing dates in the Jalali calendar and `Asia/Tehran`; API instants remain ISO-8601 UTC. - Do not concatenate translated fragments. Write complete Persian strings with correct نیم‌فاصله. - Authentication errors must not reveal whether a username exists. @@ -149,6 +182,7 @@ Navigation labels should describe merchant tasks, not internal database concepts | Version | Date | Decision | |---|---|---| +| 0.3.0 | 2026-08-20 | Approved Iran Yekan X Professional FaNum variable typography, Persian digits, Rial-only values, and first-class light/dark semantic themes. Added the deterministic insight eligibility threshold. | | 0.2.4 | 2026-08-20 | Fixed the static-demo default period, comparison fallback, overview KPI priority, and safe read-only drill-down scope; added accurate `NoAttempt`/`InBank` stage language from the CTO data guide. | | 0.2.3 | 2026-08-20 | Kept authentication UX intentionally demo-sized: generic and throttled username/password login now, with a clean replacement boundary for ZarinPal SSO after acceptance. | | 0.2.2 | 2026-08-20 | Defined backend-owned session UX: authentication tokens remain inaccessible to the frontend, and expiry/revocation returns the user to login with a generic, accessible message. | @@ -165,9 +199,6 @@ Navigation labels should describe merchant tasks, not internal database concepts Any material token/component/interaction change increments this version and adds a row describing why it changed. A surprising or difficult-to-reverse change also receives an ADR. -## Open design decisions before approval +## Open design decisions before implementation planning -1. Licensed Yekan-family assets vs open `Vazirmatn` fallback. -2. Persian or Latin digits for dashboard values. -3. Rial-only vs Toman display and conversion wording. -4. Whether a dark theme is required; it is not assumed merely because the public site uses dark feature sections. +None. Licensed font-file delivery remains an implementation input, not an unresolved design decision. diff --git a/docs/DASHBOARD-SPEC.md b/docs/DASHBOARD-SPEC.md index 0cc83ce..5394a11 100644 --- a/docs/DASHBOARD-SPEC.md +++ b/docs/DASHBOARD-SPEC.md @@ -43,6 +43,9 @@ Give a non-technical merchant a fast, trustworthy Persian RTL view of payment pe 25. First dashboard access defaults to the latest complete Jalali month containing data. The comparison is the immediately previous equivalent complete month; if unavailable/empty, the UI explicitly says comparison is unavailable and does not silently substitute another baseline. 26. Overview KPI order is bank-paid amount, bank-paid session count, payment success rate, then verification completion. Retry, processing-time, and fee analysis follow below. 27. Read-only drill-downs are available for unverified-paid, failed, and retried sessions. They exclude payer tokens and peer-level data and may distinguish zero-attempt/reached-bank stages without inventing failure causes. +28. A deterministic insight is promoted into an actionable card only when at least 100 relevant sessions contribute and the comparison changes by at least 3 percentage points for a rate or 20 percent for an amount/count. Smaller changes remain available as metrics without an action claim. +29. All merchant-facing values use Persian digits and monetary values use Rial only. Technical identifiers remain canonical and are directionally isolated. +30. Light and dark modes are supported from the first release through the semantic token contract in `DESIGN.md`; system preference is the default and an explicit accessible user choice persists locally. ## Frontend architecture baseline @@ -60,6 +63,9 @@ Give a non-technical merchant a fast, trustworthy Persian RTL view of payment pe - Critical flows work at 360 px, 768 px, 1024 px, and a wide desktop viewport in RTL. - Login, navigation drawer, filters, chart alternatives, and logout are keyboard complete and screen-reader understandable. - Values visibly specify period and unit; missing/suppressed/zero states are distinct. +- Merchant-facing digits are Persian, money is labeled in Rial only, and technical identifiers remain uncorrupted in RTL layouts. +- Light and dark modes both meet the documented contrast, focus, chart, state, and reduced-motion requirements at all acceptance viewports, with no first-paint theme flash. +- Below-threshold comparisons never produce actionable insight language, while qualifying insight cards expose their numeric evidence and limitation. - Success views include `Paid` plus `Verified`, verification-completion views separate them, and unverified paid sessions are never styled or described as failures. - Paid volume and merchant fee values reconcile at session grain; fee totals remain unchanged when a session has additional attempts. - Retry recovery includes eventual `Paid` or `Verified`, and timing percentiles never treat missing values as zero. @@ -85,5 +91,5 @@ Give a non-technical merchant a fast, trustworthy Persian RTL view of payment pe - Approved analytics interface contract for the metric catalogue and cohort policy - Approved authentication/session API - OpenAPI/interface contract and error format -- Decisions listed in `DESIGN.md` +- Delivery of the licensed Iran Yekan X Professional FaNum variable webfont - Static adapter fallback/routing behavior under the final Nginx configuration