docs(design): define typography and dark theme

This commit is contained in:
2026-08-20 17:40:08 +03:30
parent a723e272d4
commit 3fcbca8f8e
2 changed files with 48 additions and 11 deletions

View File

@@ -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.

View File

@@ -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