",
+ "css": ".ds-table { width:100%; border-collapse:collapse; border:1px solid var(--border,#e4e4e7); border-radius:8px; overflow:hidden; background:var(--card,#fff); color:var(--card-foreground,#18181b); font:400 14px/1.75 IRANYekanX,Tahoma,sans-serif; } .ds-table th,.ds-table td { padding:12px; text-align:right; border-bottom:1px solid var(--border,#e4e4e7); } .ds-table th { background:var(--muted,#f3f4f6); color:var(--muted-foreground,#71717a); font-size:12px; font-weight:600; } .ds-table tbody tr { transition:background-color 160ms ease; } .ds-table tbody tr:hover { background:color-mix(in srgb,var(--muted,#f3f4f6) 60%,transparent); } .ds-table td[dir=ltr] { text-align:left; font:500 12px/1.5 ui-monospace,monospace; } .ds-table a { border-radius:4px; color:inherit; text-decoration:none; } .ds-table a:hover { color:var(--primary,#0a33ff); text-decoration:underline; text-underline-offset:4px; } .ds-table a:focus-visible { outline:3px solid var(--ring,#0a33ff); outline-offset:2px; } .ds-verified { display:inline-flex; min-height:20px; align-items:center; padding:2px 8px; border:1px solid #a7f3d0; border-radius:6px; background:#ecfdf5; color:#047857; font-size:12px; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "The Calm Merchant Control Room",
+ "overview": "This is a Persian-first merchant workspace built for repeated operational scanning. It feels calm rather than promotional: compact controls, exact figures, plain-language explanations, and crisp bordered surfaces let a merchant understand recent performance before drilling into transactions, payment attempts, fees, or peer context.\n\nThe world is recognizably ZarinPal through its wordmark, action blue, small yellow brand accent, right-to-left shell, and familiar right-side navigation. Density is deliberate but never cramped. The first viewport establishes scope in the fixed top bar and physical-right sidebar, then leads with a page heading, period control, KPI row, and payment-history chart. Motion is limited to state feedback, shell transitions, and the initial chart trace.",
+ "keyCharacteristics": [
+ "Persian-first RTL structure with Iran Yekan X FaNum and tabular merchant-facing numerals.",
+ "Fixed 56px top bar, physical-right desktop sidebar, and mobile terminal selector in the navbar.",
+ "Low-radius white or near-black financial surfaces separated primarily by 1px borders.",
+ "ZarinPal blue reserved for actions, selection, navigation, chart emphasis, and focus.",
+ "Semantic success, warning, and danger color always accompanied by words, icons, or both.",
+ "Encouraging, factual summaries that move from recent performance to inspectable evidence."
+ ],
+ "rules": [
+ {
+ "name": "The One Blue Voice Rule",
+ "body": "Blue signals an action, current scope, active navigation, focus, or the primary data series; it does not wash large passive regions.",
+ "section": "colors"
+ },
+ {
+ "name": "The Meaning Needs Language Rule",
+ "body": "Success, warning, and danger color reinforce a visible label, icon, or explanation and never carry status alone.",
+ "section": "colors"
+ },
+ {
+ "name": "The Persian Measure Rule",
+ "body": "Merchant-facing amounts, counts, percentages, dates, and chart values use Persian digits; technical identifiers remain canonical Latin inside isolated left-to-right spans.",
+ "section": "typography"
+ },
+ {
+ "name": "The Weight Before Scale Rule",
+ "body": "The dense workspace creates hierarchy first with 500, 700, and 800 weights, then with a compact 12/14/15/20/24px size ladder.",
+ "section": "typography"
+ },
+ {
+ "name": "The Scope Before Evidence Rule",
+ "body": "Terminal and period scope must be visible before KPIs, charts, tables, or attempt history; desktop terminal scope belongs in the right sidebar and narrow-screen terminal scope belongs in the navbar.",
+ "section": "layout"
+ },
+ {
+ "name": "The Border Carries Structure Rule",
+ "body": "Use the 1px border and tonal layers for resting structure; reserve conspicuous shadows for content that actually floats above the shell.",
+ "section": "elevation"
+ },
+ {
+ "name": "The Data Surface Radius Rule",
+ "body": "Repeated financial surfaces use 8px corners and a visible border; rounding must not make tables, KPIs, or filters feel like marketing cards.",
+ "section": "shapes"
+ }
+ ],
+ "dos": [
+ "Do keep the terminal selector in the physical-right desktop sidebar and in the narrow-screen navbar.",
+ "Do lead with the selected period, four ordered KPIs, a plain-language takeaway, and then inspectable chart or table evidence.",
+ "Do use action blue for the current scope, primary series, focus, and actionable controls.",
+ "Do keep cards at 8px with a 1px border and use 16px KPI padding on phones, increasing to 20px from 640px.",
+ "Do preserve Persian digits, Rial units, Jalali dates, RTL flow, and left-to-right isolation for technical identifiers.",
+ "Do provide text, icons, focus states, and accessible alternatives anywhere color or a chart carries meaning."
+ ],
+ "donts": [
+ "Don't turn yellow into a broad dashboard fill or use it for body text on light surfaces.",
+ "Don't use pill geometry for cards, tables, inputs, or ordinary navigation rows.",
+ "Don't add decorative gradients, oversized hero type, or marketing-page whitespace to authenticated analytics routes.",
+ "Don't use shadow as the primary boundary between resting financial surfaces.",
+ "Don't animate the page as a spectacle; keep motion to state feedback, the shell, overlays, and the chart trace, and honor reduced motion.",
+ "Don't hide status, scope, units, formulas, or important chart values behind color or pointer-only interaction."
+ ]
+ }
+}
diff --git a/DESIGN.md b/DESIGN.md
index 5c86dd3..505bd10 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -1,211 +1,318 @@
-# Design System — ZarinPal Merchant Analytics
+---
+name: ZarinPal Merchant Analytics
+description: Calm Persian-first merchant intelligence in a crisp, dense financial workspace.
+colors:
+ action-blue: '#0A33FF'
+ action-blue-night: '#6F8DFF'
+ action-tint-light: '#EEF1FF'
+ action-tint-night: '#202950'
+ brand-yellow: '#FFD400'
+ workspace-light: '#F5F5F6'
+ surface-light: '#FFFFFF'
+ nested-light: '#F3F4F6'
+ ink-light: '#18181B'
+ muted-ink-light: '#71717A'
+ border-light: '#E4E4E7'
+ workspace-night: '#101012'
+ surface-night: '#19191C'
+ ink-night: '#FAFAFA'
+ muted-ink-night: '#A1A1AA'
+ border-night: '#3A3A3D'
+ success-light: '#248061'
+ warning-light: '#8A5300'
+ danger-light: '#C93637'
+ success-night: '#6FD6AE'
+ warning-night: '#F3BE5B'
+ danger-night: '#FF8A8B'
+typography:
+ page-heading:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '1.25rem'
+ fontWeight: 800
+ lineHeight: 1.4
+ letterSpacing: '-0.025em'
+ section-heading:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '1.25rem'
+ fontWeight: 700
+ lineHeight: 1.5
+ metric:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '1.5rem'
+ fontWeight: 800
+ lineHeight: 1.25
+ fontFeature: 'tnum'
+ body:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '0.9375rem'
+ fontWeight: 400
+ lineHeight: 1.8
+ supporting:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '0.875rem'
+ fontWeight: 400
+ lineHeight: 1.75
+ label:
+ fontFamily: 'IRANYekanX, Tahoma, sans-serif'
+ fontSize: '0.75rem'
+ fontWeight: 500
+ lineHeight: 1.5
+rounded:
+ compact: '6px'
+ control: '7px'
+ surface: '8px'
+ pill: '9999px'
+spacing:
+ 1: '4px'
+ 2: '8px'
+ 3: '12px'
+ 4: '16px'
+ 5: '20px'
+ 6: '24px'
+ 8: '32px'
+components:
+ button-primary:
+ backgroundColor: '{colors.action-blue}'
+ textColor: '{colors.surface-light}'
+ typography: '{typography.label}'
+ rounded: '{rounded.surface}'
+ padding: '0 10px'
+ height: '32px'
+ button-ghost:
+ backgroundColor: 'transparent'
+ textColor: '{colors.ink-light}'
+ typography: '{typography.label}'
+ rounded: '{rounded.surface}'
+ padding: '0 10px'
+ height: '32px'
+ card:
+ backgroundColor: '{colors.surface-light}'
+ textColor: '{colors.ink-light}'
+ typography: '{typography.body}'
+ rounded: '{rounded.surface}'
+ padding: '20px'
+ input:
+ backgroundColor: 'transparent'
+ textColor: '{colors.ink-light}'
+ typography: '{typography.supporting}'
+ rounded: '{rounded.surface}'
+ padding: '4px 10px'
+ height: '32px'
+ nav-active:
+ backgroundColor: '{colors.action-tint-light}'
+ textColor: '{colors.action-blue}'
+ typography: '{typography.supporting}'
+ rounded: '{rounded.compact}'
+ padding: '8px 12px'
+ height: '44px'
+---
-- Status: Approved baseline — implementation authorized 2026-08-20
-- Version: 0.4.0
-- Last updated: 2026-08-20
-- Direction: Persian-first RTL
+# Design System: ZarinPal Merchant Analytics
-## Design intent
+## Overview
-Match the recognizable product language of ZarinPal without copying inaccessible proprietary assets: calm financial confidence, generous whitespace, crisp white surfaces, strong blue actions, restrained yellow accents, dark ink sections, rounded geometry, and dense data presented in plain language.
+**Creative North Star: "The Calm Merchant Control Room"**
-The authenticated panel screenshots are the primary behavioral reference: fixed right navigation on desktop, right-side drawer on narrow screens, light gray workspace, white cards, compact outlined controls, blue active states, and direct numerical summaries. The public-site screenshots inform larger radius, dark promotional surfaces, and spacing—but the dashboard prioritizes speed and legibility over marketing spectacle.
+This is a Persian-first merchant workspace built for repeated operational scanning. It feels calm rather than promotional: compact controls, exact figures, plain-language explanations, and crisp bordered surfaces let a merchant understand recent performance before drilling into transactions, payment attempts, fees, or peer context.
-## Foundations
+The world is recognizably ZarinPal through its wordmark, action blue, small yellow brand accent, right-to-left shell, and familiar right-side navigation. Density is deliberate but never cramped. The first viewport establishes scope in the fixed top bar and physical-right sidebar, then leads with a page heading, period control, KPI row, and payment-history chart. Motion is limited to state feedback, shell transitions, and the initial chart trace.
-### Color tokens
+**Key Characteristics:**
-| Token | Value | Use |
-| ----------------------- | --------: | ---------------------------------------------------------------- |
-| `--color-brand-600` | `#0A33FF` | Primary actions, active navigation, links, selected chart series |
-| `--color-brand-700` | `#0828CC` | Hover/pressed brand state |
-| `--color-accent-400` | `#FFD400` | Small ZarinPal-like accent, highlights; never body text on white |
-| `--color-ink-950` | `#19191A` | Primary text, dark panels |
-| `--color-ink-700` | `#3D3D42` | Secondary strong text |
-| `--color-ink-500` | `#6B6B73` | Supporting text |
-| `--color-canvas` | `#F1F1F3` | Application background |
-| `--color-surface` | `#FFFFFF` | Cards, drawer, dialogs |
-| `--color-surface-muted` | `#F7F7F8` | Nested panels, table headers |
-| `--color-border` | `#E3E3E7` | Dividers and neutral outlines |
-| `--color-success-600` | `#248061` | Success states and positive semantic data |
-| `--color-warning-600` | `#A86700` | Warnings; paired with text/icon, never color alone |
-| `--color-danger-500` | `#FF5C5D` | Errors and destructive actions |
-| `--color-info-surface` | `#E7F2FF` | Informational callouts |
+- Persian-first RTL structure with Iran Yekan X FaNum and tabular merchant-facing numerals.
+- Fixed 56px top bar, physical-right desktop sidebar, and mobile terminal selector in the navbar.
+- Low-radius white or near-black financial surfaces separated primarily by 1px borders.
+- ZarinPal blue reserved for actions, selection, navigation, chart emphasis, and focus.
+- Semantic success, warning, and danger color always accompanied by words, icons, or both.
+- Encouraging, factual summaries that move from recent performance to inspectable evidence.
-All text/background pairs must meet WCAG 2.2 AA contrast. Chart palettes require patterns, labels, or shapes in addition to hue.
+### Approved baseline provenance
-### Theme contract
+The approved Persian-first baseline remains authoritative where the shipped artifact confirms it: the licensed font and license file stay tracked; light and dark themes remain semantic variants; values use Persian digits and Rial; merchant dates use the Jalali calendar and Asia/Tehran; RTL, bidi isolation, WCAG 2.2 AA, keyboard operation, reduced motion, safe peer comparisons, and traceable metrics remain binding. The artifact now resolves provisional visual guidance into the concrete tokens and component rules recorded here.
-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.
+| Version | Date | Decision |
+| ------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 0.6.0 | 2026-08-20 | Added full-span Jalali period selection, unified transaction status/amount controls, persistent skeleton states, custom RTL dropdowns, semantic peer/retry tones, and fee-share visualization; tightened the narrow navbar and 600px KPI breakpoint. |
+| 0.5.0 | 2026-08-20 | Reconciled the approved baseline with the shipped routed frontend and desktop, mobile, and 639px review captures; converted the file to canonical DESIGN.md format and added the design sidecar. |
+| 0.4.0 | 2026-08-20 | Adopted the current ZarinPal panel as the closer interaction reference: top wordmark bar, 256/72px collapsible right sidebar, 8px data surfaces, routed sections, URL-backed period and terminal filters, payment-history chart, transaction table, attempt history, formula help, and supportive insights. |
+| 0.3.3 | 2026-08-20 | Replaced merchant/cohort revision freshness language with the approved immutable completed-import dataset snapshot. |
+| 0.3.2 | 2026-08-20 | Made the licensed Iran Yekan X FaNum variable WOFF2 and its required license file Git-tracked frontend assets. |
+| 0.3.1 | 2026-08-20 | Verified the purchased Iran Yekan X Professional FaNum variable assets and added merchant-owned detail CSV export as an MVP contract. |
+| 0.3.0 | 2026-08-20 | Approved Iran Yekan X Professional FaNum, Persian digits, Rial-only values, first-class light/dark themes, and the deterministic insight threshold. |
+| 0.2.4 | 2026-08-20 | Fixed the default period, comparison fallback, KPI priority, read-only drill-down scope, and accurate NoAttempt/InBank stage language. |
+| 0.2.3 | 2026-08-20 | Kept authentication UX intentionally demo-sized with a clean replacement boundary for ZarinPal SSO. |
+| 0.2.2 | 2026-08-20 | Defined backend-owned session UX and generic accessible expiry/revocation handling. |
+| 0.2.1 | 2026-08-20 | Defined peer cards as an equal-per-merchant median plus direction-aware percentile. |
+| 0.2.0 | 2026-08-20 | Corrected payment lifecycle semantics: Paid and Verified are bank-payment successes, Paid remains unverified, and session fees are real Rial amounts. |
+| 0.1.9 | 2026-08-20 | Added rate-limit UX, SSE heartbeat/recovery, replay, and failed-section-only retry. |
+| 0.1.8 | 2026-08-20 | Fixed the parallel analytics section identifiers, cache, idempotent start/SSE flow, and section timeout/retry behavior. |
+| 0.1.7 | 2026-08-20 | Defined partial, parallel section delivery through authenticated SSE. |
+| 0.1.6 | 2026-08-20 | Replaced time-based analytics freshness with lazy calculation and defined Saturday–Friday weeks. |
+| 0.1.5 | 2026-08-20 | Limited period controls to daily, weekly, Jalali-month, and Persian-season views. |
+| 0.1.2 | 2026-08-20 | Added the insufficient-data state and visibility threshold for anonymized peer comparisons. |
+| 0.1.1 | 2026-08-20 | Fixed merchant-facing time presentation to the Jalali calendar in Asia/Tehran with UTC API instants. |
+| 0.1.0 | 2026-08-20 | Established the ZarinPal-inspired RTL foundation, responsive shell, core components, accessibility, chart, and motion rules. |
-| 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 |
+## Colors
-- 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.
+The palette is a neutral financial canvas with one decisive blue voice, a very small yellow identity accent, and restrained semantic color.
-### Typography
+### Primary
-- 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.
-- The purchased font is a Git-tracked project asset. Keep `static/fonts/IRANYekanXVFaNumVF.woff2` and its required adjacent `static/fonts/FontLicense.txt` in the frontend repository so local, CI, and Docker builds use the same reviewed bytes without downloading from ZarinPal or another CDN.
-- The tracked font is the only approved primary font asset for implementation. Do not replace, rename, subset, regenerate, or add other files from the purchased package without updating this specification and confirming license compliance.
-- 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.
-- Section heading: 20–24 px, weight 700.
-- Page heading: 28–32 px desktop, 24–28 px mobile, weight 700–800.
-- Avoid ultralight weights and excessive bolding. Keep Persian punctuation and half-spaces correct.
+- **ZarinPal Action Blue:** Drives selected range controls, active navigation, interactive chart marks, links, and primary focus. Its night-theme companion is brighter so the same roles stay legible on near-black surfaces.
+- **Action Tint:** Supports active navigation and lightly emphasized information without turning whole cards blue.
-### Spacing, shape, elevation
+### Secondary
-- Base spacing unit: 4 px; common sequence `4, 8, 12, 16, 24, 32, 48, 64`.
-- Control height: 40 px desktop; interactive touch target at least 44×44 px on touch layouts.
-- Card radius: 8 px for dense dashboard cards and tables; 12 px only for spacious explanatory or empty-state surfaces.
-- Button/input radius: 6–8 px; pill radius only for compact statuses and segmented controls.
-- Card border is preferred over shadow. Default elevation is subtle (`0 1px 3px rgb(25 25 26 / 0.08)`); overlays may use stronger elevation.
-- Content width is bounded for scanning; tables/charts may use the full available dashboard column.
+- **ZarinPal Yellow:** Appears in the supplied wordmark and text selection. It is an identity spark, not a general dashboard accent or text color.
-## Layout and responsiveness
+### Tertiary
-### Desktop (≥ 1024 px)
+- **Success:** Successful payments and ready states.
+- **Warning:** Incomplete verification, suppression, or caution.
+- **Danger:** Errors, failed states, and destructive meaning.
-- Fixed right sidebar, 256 px expanded and 72 px collapsed, with icon-only collapse matching the supplied panel reference.
-- A 56 px top utility bar spans the viewport and carries the supplied ZarinPal wordmark, sidebar trigger, theme, notification, and account actions.
-- Main content uses a centered, fluid container with 24–32 px gutters.
-- Overview uses a 12-column grid. Primary analytics get 8 columns; secondary summaries get 4 where useful.
-- Do not create large empty regions merely to mimic the reference screenshot; use the density required by real content.
+### Neutral
-### Tablet (768–1023 px)
+- **Workspace:** A cool, near-white light canvas and a near-black night canvas frame the application.
+- **Surface:** Cards, navbar, sidebar, menus, and overlays sit one tonal step above the workspace.
+- **Nested Surface:** Table headers, secondary controls, progress tracks, and quiet hover states use the muted neutral layer.
+- **Ink and Muted Ink:** Strong figures and headings use high-contrast ink; explanations, metadata, and control labels use muted ink.
+- **Border:** A cool 1px line is the default separator between data surfaces and within tables.
-- Sidebar becomes an overlay drawer from the right.
-- Two-column cards collapse according to content, not arbitrary device labels.
-- Tables may scroll horizontally only after priority columns and responsive alternatives are considered.
+### Named Rules
-### Mobile (< 768 px)
+**The One Blue Voice Rule.** Blue signals an action, current scope, active navigation, focus, or the primary data series; it does not wash large passive regions.
-- Single-column content, 16 px outer gutter.
-- Right-side navigation drawer traps focus, closes with Escape/backdrop, restores focus to its trigger, and locks background scroll.
-- KPI pairs stack; charts remain at least 240 px tall and expose an accessible data table/summary.
-- Sticky actions must not cover content or browser safe areas.
+**The Meaning Needs Language Rule.** Success, warning, and danger color reinforce a visible label, icon, or explanation and never carry status alone.
-## Navigation and information architecture
+## Typography
-Authenticated routes:
+**Display Font:** Iran Yekan X Professional FaNum variable (IRANYekanX), with Tahoma and sans-serif fallbacks.
-1. ورود (login)
-2. پیشخوان (`/dashboard`)
-3. تراکنشها (`/transactions` and `/transactions/[sessionId]`)
-4. عملکرد پرداخت (`/payments`)
-5. کارمزد و درآمد (`/fees`)
-6. مقایسه با همگروهها (`/peers`)
-7. کیفیت داده / درباره شاخصها (`/methodology`)
-8. حساب کاربری / خروج
+**Body Font:** Iran Yekan X Professional FaNum variable (IRANYekanX), with Tahoma and sans-serif fallbacks.
-Navigation labels should describe merchant tasks, not internal database concepts. Active location is conveyed by color plus a bar/background and `aria-current`.
+**Character:** One Persian UI family keeps the workspace cohesive and highly legible. Weight, size, tabular figures, and spacing create hierarchy; ornamental display typography is outside this product world.
-## Core component contracts
+### Hierarchy
-- **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.
-- **Data table:** semantic headers, server pagination/sorting, row actions named for screen readers, responsive priority, empty/error states.
-- **Analytics export:** eligible merchant-owned detail views expose an explicit CSV export action that applies the current approved filters and clearly states period, unit, generation state, and failure/retry behavior. Aggregate-only cards and peer data are not exported unless separately specified.
-- **Filter bar:** URL-backed state, explicit apply/reset behavior, and approved daily, weekly, Jalali-month, and Persian-season periods with unambiguous comparison baselines.
-- **Status badge:** text plus icon/shape; color reinforces but never carries meaning alone.
-- **Feedback:** inline field errors, page-level error boundary, skeleton only when layout is known, retry action for recoverable failures.
-- **Rate-limited state:** explain when an analytics start, stream, or manual retry is temporarily limited; preserve ready content, honor the server `Retry-After`, and never run a client-side retry loop.
+- **Page Heading** (800, 20px; 24px from 640px, 1.4): Routed page identity at the top of the workspace.
+- **Section Heading** (700, 20px; 24px from 640px, 1.5): Major analytics modules and methodology groups.
+- **Metric** (800, 24px, 1.25): KPI values, amounts, rates, and transaction totals; use tabular numerals.
+- **Body** (400, 15px, 1.8): Default interface copy and longer Persian explanations.
+- **Supporting** (400, 14px, usually 1.75): Descriptions, filters, table content, and insight summaries.
+- **Label** (500–600, 12px, 1.5): Metadata, compact controls, badges, terminal context, and chart annotations; never go below 11px, which is reserved for the two-line terminal identity in the desktop sidebar.
-## Data visualization
+### Named Rules
-- Lead with a written takeaway and exact KPI; charts support the conclusion rather than decorate it.
-- Default comparisons: current period vs previous equivalent period and merchant vs eligible merchants in the same category and selected period. Peer cards show the merchant-weighted cohort median and the active merchant's performance percentile; each peer contributes one value regardless of transaction volume. A higher displayed percentile always means better, so processing-time and fee-rate ranks are direction-adjusted. Peer values appear only when at least 10 other merchants and 1,000 combined peer sessions contribute; otherwise show a clear insufficient-data state.
-- Always show unit, timezone/calendar, exact reporting period, comparison baseline, sample size, freshness, and missing-data caveat where material.
-- Success/failure colors remain semantic and consistent. Never use a rainbow categorical palette for ordered values.
-- 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.
-- First load selects the latest complete Jalali month containing data and compares only with the immediately previous equivalent period. An absent baseline has an explicit unavailable state.
-- Overview leads with bank-paid amount, bank-paid session count, success rate, and verification completion. Retry, timing, and fee modules follow in that order of detail.
-- Drill-down tables for unverified-paid, failed, and retried sessions are read-only. They never reveal payer tokens or peer rows; `NoAttempt` and `InBank` are stage labels, not invented failure causes.
+**The Persian Measure Rule.** Merchant-facing amounts, counts, percentages, dates, and chart values use Persian digits; technical identifiers remain canonical Latin inside isolated left-to-right spans.
-## Motion
+**The Weight Before Scale Rule.** The dense workspace creates hierarchy first with 500, 700, and 800 weights, then with a compact 12/14/15/20/24px size ladder.
-- Functional motion only: drawer/dialog transitions, focus/selection feedback, and short chart/filter state changes.
-- Typical duration 120–220 ms; enter may be slightly slower than exit.
-- Prefer opacity and transform. Avoid layout-heavy animation and dashboard-wide scroll reveals.
-- Honor `prefers-reduced-motion` by removing nonessential movement.
-- No third-party scroll-reveal library is planned for the authenticated panel. Reconsider only for a separately scoped marketing surface.
+## Layout
-## Persian, direction, and formatting
+The application is a bounded RTL shell. A fixed 56px top navbar spans the viewport. At 768px and wider, a physical-right sidebar is 256px expanded and 72px collapsed; its top card holds merchant identity and terminal selection. The content column is centered at a maximum of 1220px with 16px gutters, increasing to 24px at 640px and 32px at 1024px.
-- 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.
-- 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.
+The overview begins with a compact title block and full-width range selector. KPI cards form two columns from 600px and four columns from 1024px; the payment-history chart follows as the dominant full-width surface. Deeper pages reuse the same header, filters, bounded cards, and horizontally scrollable tables.
-## Accessibility and interaction
+Below 768px, the sidebar yields to the navbar. Terminal selection moves into the top bar beside the wordmark and theme utility, while content uses 16px gutters. The account initial is intentionally hidden below 640px so terminal scope and navigation never overflow. KPI cards remain stacked on phone widths and become a compact 2×2 set from 600px, preserving scan order while bringing the payment-history chart into view sooner.
-- WCAG 2.2 AA target.
-- Visible `:focus-visible` ring on every interactive element.
-- Semantic buttons/links/forms before ARIA; icons have accessible names or are hidden when decorative.
-- All functionality is keyboard operable. Dialogs/drawers manage focus correctly.
-- Validation is associated with fields and announced; asynchronous updates use restrained live regions.
-- Loading indicators expose status text; avoid endless skeletons.
-- Destructive or consequential actions require clear confirmation and recovery where possible.
+**The Scope Before Evidence Rule.** Terminal and period scope must be visible before KPIs, charts, tables, or attempt history; desktop terminal scope belongs in the right sidebar and narrow-screen terminal scope belongs in the navbar.
-## Current decisions and change log
+## Elevation & Depth
-| Version | Date | Decision |
-| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| 0.4.0 | 2026-08-20 | Adopted the supplied current ZarinPal panel as the closer interaction reference: added the top wordmark bar, 256/72 px collapsible right sidebar, less-rounded 8 px data surfaces, routed sections, URL-backed period/terminal filters, payment-history chart, filtered transaction table, routed attempt history, formula help, and supportive human-readable insights. |
-| 0.3.3 | 2026-08-20 | Replaced merchant/cohort revision freshness language with the approved immutable completed-import dataset snapshot. This changes trace labels only; cache age still does not imply stale data. |
-| 0.3.2 | 2026-08-20 | Made the licensed Iran Yekan X FaNum variable WOFF2 and its required license file Git-tracked frontend assets, eliminating a separate private build-injection path. |
-| 0.3.1 | 2026-08-20 | Verified the purchased Iran Yekan X Professional FaNum variable assets and required a private, reproducible injection path for public repositories. Added merchant-owned detail CSV export as an MVP component contract. |
-| 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. |
-| 0.2.1 | 2026-08-20 | Defined same-category peer cards as an equal-per-merchant median plus direction-aware percentile. This prevents the dataset's largest merchants from dominating what should represent a typical peer. |
-| 0.2.0 | 2026-08-20 | Corrected payment lifecycle semantics from CTO guidance: `Paid` and `Verified` are bank-payment successes, `Paid` remains unverified, and session-level fees are real Rial amounts. |
-| 0.1.9 | 2026-08-20 | Added rate-limit UX, 15-second SSE heartbeat/recovery, one-hour replay, and failed-section-only manual retry. |
-| 0.1.8 | 2026-08-20 | Fixed the four parallel section ids, 30-day cache, idempotent start/SSE flow, and section timeout/retry behavior. |
-| 0.1.7 | 2026-08-20 | Moved analytics results to high-TTL Redis and defined partial, parallel section delivery through authenticated SSE. |
-| 0.1.6 | 2026-08-20 | Replaced time-based analytics freshness with lazy first-access calculation and revision-based merchant/cohort cache states; defined Saturday–Friday weeks. |
-| 0.1.5 | 2026-08-20 | Removed holiday/event reporting; limited period controls to daily, weekly, Jalali-month, and Persian-season views. |
-| 0.1.2 | 2026-08-20 | Added the insufficient-data state and visibility threshold for anonymized peer comparisons. |
-| 0.1.1 | 2026-08-20 | Fixed merchant-facing time presentation to the Jalali calendar in `Asia/Tehran`, with explicit UTC API instants. |
-| 0.1.0 | 2026-08-20 | Established ZarinPal-inspired RTL foundation from supplied public/panel screenshots; fixed SvelteKit/Svelte/TypeScript/Tailwind stack; defined responsive shell, core components, accessibility, chart, and motion rules. |
+The system is flat by default. Workspace, card, nested surface, and border tones establish hierarchy; the repeated low shadow is only a small reinforcement on cards and controls. The fixed top bar uses an even quieter separator shadow, while tooltips, sheets, and dialogs can use stronger overlay elevation.
-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.
+### Shadow Vocabulary
-## Open design decisions before implementation planning
+- **Surface Low** (0 1px 2px 0 rgb(0 0 0 / 0.05)): KPI cards, charts, filter bars, and repeated dashboard containers.
+- **Navbar Hairline** (0 1px 2px rgba(0, 0, 0, 0.03)): Fixed top bar only.
+- **Overlay High** (0 20px 25px -5px rgb(0 0 0 / 0.10), 0 8px 10px -6px rgb(0 0 0 / 0.10)): Floating chart tooltip and modal-level content only.
-None. The approved licensed font and its license file are already tracked at the specified frontend paths.
+### Named Rules
+
+**The Border Carries Structure Rule.** Use the 1px border and tonal layers for resting structure; reserve conspicuous shadows for content that actually floats above the shell.
+
+## Shapes
+
+The form language is low-radius and precise. Data cards, filter bars, inputs, selects, and standard buttons resolve around 8px; dense internal controls and active navigation use 6–7px. Status badges may be pills because their silhouette communicates compact state. Avatars and small identity marks may be circular. Large soft containers are limited to modal-scale overlays or empty states and must not migrate into the dense dashboard.
+
+**The Data Surface Radius Rule.** Repeated financial surfaces use 8px corners and a visible border; rounding must not make tables, KPIs, or filters feel like marketing cards.
+
+## Components
+
+### Buttons
+
+- **Shape:** Compact, bordered or filled, with 6–8px corners and 32–36px desktop height; touch layouts raise interactive targets to at least 44px.
+- **Primary:** Action blue with white content in light mode and dark ink on the brighter night action blue.
+- **Hover / Focus:** Hover changes tone; active state moves down by 1px where the action is press-like. Keyboard focus uses a visible 3px semantic blue ring. State transitions stay around 160ms.
+- **Ghost:** Transparent at rest, muted-neutral on hover, used for inactive range choices and utility icons.
+
+### Chips
+
+- **Style:** Compact 20px badges use 12px medium text. Semantic badges may be pill-shaped; date and percentile chips use the tighter control radius.
+- **State:** Selected filters use filled blue plus aria-pressed; ready, warning, and failure labels pair color with explicit text.
+
+### Cards / Containers
+
+- **Corner Style:** Dense 8px corners.
+- **Background:** Theme surface over theme workspace; lightly emphasized insight cards may use an action or semantic tint.
+- **Shadow Strategy:** Surface Low only, with border/ring structure always present.
+- **Border:** One cool neutral pixel; semantic borders are translucent accents on error or insight surfaces.
+- **Internal Padding:** 20px is the desktop KPI rhythm and contracts to 16px on phones; 24px supports explanatory cards, and charts may use 12px horizontally on narrow screens.
+
+### Inputs / Fields
+
+- **Style:** Transparent or workspace-toned field, 1px input border, 6–8px radius, 32–36px desktop height, 44px touch height, and compact 12–14px text. Terminal and status selects use the same RTL popover primitive rather than the browser-native menu.
+- **Focus:** Border shifts to the ring color and gains a visible 3px half-opacity blue ring.
+- **Error / Disabled:** Error uses danger border plus ring; disabled fields reduce opacity and preserve legible text.
+
+### Navigation
+
+- **Desktop:** The fixed right sidebar is white/near-black with 44px task rows, 19px outline icons, 13px labels, and an action-tint active background with blue content.
+- **Mobile:** The fixed top bar retains the wordmark, terminal selector, theme action, and menu trigger. The account initial appears from 640px and is intentionally omitted on narrower phones.
+- **State:** Current routes use a background plus blue text and remain identified with aria-current or equivalent active state.
+
+### KPI Card
+
+The signature KPI card pairs a muted label and optional formula help with a semantic 20px outline icon, a 24px extra-bold tabular value, and one short 12px explanatory line. It uses 16px padding on phones and 20px above that, and may lift 2px over 200ms on hover without changing its information hierarchy.
+
+### Reporting Scope Filter
+
+Daily, Saturday–Friday weekly, Jalali-month, and Persian-season controls share one URL-backed bar. Exact period selection opens a compact RTL popover: a four-digit Jalali year input, month navigation plus calendar grid for day/week, whole-week hover and selection, and named month/season choices. Transaction status and a two-thumb RTL amount range join the same bar after a divider. Backend-offered complete periods are the selectable authority.
+
+### Loading State
+
+Scope changes immediately replace data-dependent cards, tables, detail panels, and charts with geometry-matched skeletons. The title, selected scope, and navigation stay visible; no routed data surface collapses into an empty page while the API is working.
+
+### Payment-History Chart
+
+The signature chart is a blue line with outlined points, a low-opacity blue area, and dashed neutral gridlines. It follows a written title and reporting-period chip, provides focusable points with spoken values, and animates its initial trace once over 900ms while reduced-motion collapses the effect.
+
+### Transaction Table
+
+The table stays inside a bordered 8px surface, receives its compact filters from the shared page bar, uses aligned 14px rows and 12px identifiers/status, and exposes semantic detail links with a pointer and neutral row hover. Verified badges are green; Paid remains neutral gray. Narrow viewports may scroll the table horizontally after surrounding filters and summaries have reflowed.
+
+### Fee Share and Comparative Progress
+
+The fee page pairs numeric KPIs with a compact circular share chart. Peer progress bars move from rose through amber and sky to emerald as the direction-adjusted percentile improves. Retry guidance uses amber, sky, or emerald surfaces and changes its supportive sentence at tested recovery/sample thresholds.
+
+## Do's and Don'ts
+
+### Do:
+
+- **Do** keep the terminal selector in the physical-right desktop sidebar and in the narrow-screen navbar.
+- **Do** lead with the selected period, four ordered KPIs, a plain-language takeaway, and then inspectable chart or table evidence.
+- **Do** use action blue for the current scope, primary series, focus, and actionable controls.
+- **Do** keep cards at 8px with a 1px border and use 16px KPI padding on phones, increasing to 20px from 640px.
+- **Do** preserve Persian digits, Rial units, Jalali dates, RTL flow, and left-to-right isolation for technical identifiers.
+- **Do** provide text, icons, focus states, and accessible alternatives anywhere color or a chart carries meaning.
+
+### Don't:
+
+- **Don't** turn yellow into a broad dashboard fill or use it for body text on light surfaces.
+- **Don't** use pill geometry for cards, tables, inputs, or ordinary navigation rows.
+- **Don't** add decorative gradients, oversized hero type, or marketing-page whitespace to authenticated analytics routes.
+- **Don't** use shadow as the primary boundary between resting financial surfaces.
+- **Don't** animate the page as a spectacle; keep motion to state feedback, the shell, overlays, and the chart trace, and honor reduced motion.
+- **Don't** hide status, scope, units, formulas, or important chart values behind color or pointer-only interaction.
diff --git a/src/app.css b/src/app.css
index 639c2f0..742727c 100644
--- a/src/app.css
+++ b/src/app.css
@@ -274,3 +274,13 @@ select {
@apply font-sans;
}
}
+
+@layer base {
+ a[href],
+ button:not(:disabled),
+ select:not(:disabled),
+ summary,
+ [role='button']:not([aria-disabled='true']) {
+ cursor: pointer;
+ }
+}
diff --git a/src/lib/components/AmountRangeFilter.svelte b/src/lib/components/AmountRangeFilter.svelte
new file mode 100644
index 0000000..26bdfcf
--- /dev/null
+++ b/src/lib/components/AmountRangeFilter.svelte
@@ -0,0 +1,173 @@
+
+
+
+
+
+ مبلغ
+ {#if hasFilter}{/if}
+
+
+
+
بازه مبلغ تراکنش
+
دو نشانگر را برای تعیین کمینه و بیشینه جابهجا کنید.
{formatValue(performance.data.retriedSessions.value)} مشتری دوباره تلاش کردهاند و نرخ بازیابی آنها
- {formatPercent(performance.data.retryRecoveryRate.value)} بوده است. جزئیات تراکنشهای ناموفق میتواند
- نقاط بهبود مسیر را نشان دهد.
+ {formatPercent(performance.data.retryRecoveryRate.value)} بوده است. {opportunity.copy}
{:else if kind === 'fees' && fees.state === 'READY'}
-
+
+
diff --git a/src/lib/components/AppShell.svelte b/src/lib/components/AppShell.svelte
index 8962c3b..292b391 100644
--- a/src/lib/components/AppShell.svelte
+++ b/src/lib/components/AppShell.svelte
@@ -4,7 +4,6 @@
import ActivityIcon from '@lucide/svelte/icons/activity';
import ArrowUpDownIcon from '@lucide/svelte/icons/arrow-up-down';
import BookOpenIcon from '@lucide/svelte/icons/book-open';
- import ChartNoAxesCombinedIcon from '@lucide/svelte/icons/chart-no-axes-combined';
import HandCoinsIcon from '@lucide/svelte/icons/hand-coins';
import LogOutIcon from '@lucide/svelte/icons/log-out';
import MenuIcon from '@lucide/svelte/icons/menu';
@@ -15,11 +14,11 @@
import WalletCardsIcon from '@lucide/svelte/icons/wallet-cards';
import BrandLogo from '$lib/components/BrandLogo.svelte';
+ import TerminalSelect from '$lib/components/TerminalSelect.svelte';
import { Button } from '$lib/components/ui/button';
import * as Sidebar from '$lib/components/ui/sidebar';
import { useDashboardContext } from '$lib/dashboard-context.svelte';
import { setDashboardQuery } from '$lib/dashboard-filters';
- import { formatValue } from '$lib/format';
let { children } = $props();
const dashboard = useDashboardContext();
@@ -37,7 +36,7 @@
const url = new URL(page.url);
url.pathname = path;
for (const key of [...url.searchParams.keys()]) {
- if (!['range', 'terminal'].includes(key)) url.searchParams.delete(key);
+ if (!['range', 'period', 'terminal'].includes(key)) url.searchParams.delete(key);
}
return `${url.pathname}${url.search}`;
}
@@ -49,9 +48,9 @@
);
}
- function terminalChanged(event: Event) {
+ function terminalChanged(value: string) {
const next = setDashboardQuery(page.url, {
- terminal: (event.currentTarget as HTMLSelectElement).value,
+ terminal: value,
});
void goto(`${next.pathname}${next.search}`, { keepFocus: true, noScroll: true });
}
@@ -83,19 +82,12 @@
-
+ terminals={dashboard.terminals}
+ onValueChange={terminalChanged}
+ />