diff --git a/design/README.md b/design/README.md new file mode 100644 index 0000000..8ef7df8 --- /dev/null +++ b/design/README.md @@ -0,0 +1,63 @@ +# Ganjoor design system + +This folder holds the app's design system, extracted from its code: +- `tokens.json`: colours for all six themes, type styles, spacing, radii and sizes. +- `components/`: usage guidelines for each component, with a static preview. +- `assets/`: the logo and icons as SVG. +- `wireframes/`: the tablet and foldable layout, state by state. + +The font files referenced in `tokens.json` (`fonts/…`) are the app's own, in `app/src/main/res/font/`, and the store screenshots are in `fastlane/metadata/android/fa/images/`. The previews use CSS variables named after the tokens. They render in the published design system artifact, which generates those variables from `tokens.json`. + +--- + +Ganjoor is a quiet, long-form reader for Persian poetry, made for Persian, Urdu and Arabic script. The poem is the interface: everything else gets out of its way, says plainly what it does, and works offline. The palette comes from Persian tilework, turquoise with saffron accents, alongside warm paper tones for long sessions. + +Built on Material 3 (Jetpack Compose). Tokens follow the Material colour roles one to one, so `primary` here is `MaterialTheme.colorScheme.primary` in the app. + +## Content fundamentals + +- **Persian first.** The interface defaults to Persian whatever the phone's locale, and also speaks Urdu and English. The app is called **گنجور** in every language. Languages are named in their own script (فارسی · اردو · English). +- **Right-to-left, always.** The whole interface lays out and navigates RTL, even when the UI language is English. Directional icons mirror. +- **Plain, specific, honest.** Say what happened and what to do next, without blaming the reader: "This isn't downloaded. Turn off offline mode, or download it first." is right; "Network error" is not. A setting's note says when it applies: "Applies to the dark themes; saves power on OLED screens". +- **Sentence case, no exclamation marks, no emoji.** Labels are short verbs or nouns: *Save this passage*, *Copy*, *Share*, *Download every poet*. A state replaces the verb: *Saved*. +- **Credit and disclose.** AI text is always labelled: "AI-generated by Ganjoor, not by the poet". The app is unofficial and says so in About. It is never presented as ganjoor.net's own. +- **Give the reader something useful.** Listings show each poem's opening line under its title, because "Ghazal 237" tells you nothing. + +## Visual foundations + +### Colour and themes +Six themes, all the same roles. **Light** and **Dark** use turquoise `primary` with saffron `secondary`. **Sepia** and **Sepia night** are aged-paper schemes with walnut `primary`; the night one is dark without a blue cast. **OLED black** isn't a separate palette. It is a flag on either dark theme that moves only the surfaces towards black (in Oklab) and leaves text and accents exactly as they are. That gives the two themes *Dark · OLED black* and *Sepia night · OLED black*. + +- Page ground is `surface`. Primary text is `on-surface`; secondary text and icons are `on-surface-variant`. +- `primary` is for what you can tap or what is active: tappable breadcrumbs, TextButtons, section labels, a saved bookmark, the pin mark. It is never used for running text. +- Selected chips and portrait discs use `secondary-container` with `on-secondary-container`. +- Poet cards use `surface-container-highest`; sheets use `surface-container-low`; the bottom search bar uses `surface-tonal-3`. +- `downloaded` (green) appears only with the check-circle, so the state is never shown by colour alone. +- Every text pair named in a token's notes meets 4.5:1 in all six themes. Sepia's `outline` is 3.3:1 on `surface`, which passes for borders only. + +### Typography +- **Poems are always Noto Naskh Arabic or Noto Nastaliq Urdu**, whatever the UI language. Use `poem-naskh` by default and `poem-nastaliq` when the reader picks it. The reader chooses a size from 14 to 40px (default 22) and a weight of Regular, Medium, Semibold or Bold, because thin naskh strokes wash out on a lit screen, especially in dark themes. Leading is 1.8× for naskh and 2.4× for nastaliq, whose diagonal stacking and deep descenders would otherwise clip. +- **The Persian and Urdu interface** uses the Material 3 type scale in Naskh (`title-medium`, `body-small`…). +- **The English interface** uses the same scale in **Libron**, a reading serif (`en-title-medium`…). Libron never sets a poem. + +### Layout and spacing +Values are the Compose dp values written in the code (1dp = 1px): `space-20` for the reader column and settings sheet sides, `space-12` for list sides and grid gutters, `space-8` as the default gap, and `space-6` above and below each couplet. Touch targets are `touch` (48px). The poet grid is adaptive with columns of at least `poet-grid-min`. + +**Couplets** stack on a phone: the first hemistich aligns to the start, the second to the end. Prose (Golestan, Nowruznameh) is justified. On large screens the hemistichs sit side by side (see **ColumnBrowser**). + +**One-handed reach:** the poets search lives in a bottom bar, and the settings open as a bottom sheet. + +### Shape, elevation, motion +Shapes are Material's: `radius-sm` for chips, `radius-md` for cards, `radius-xl` for sheet tops and `radius-full` for buttons and portrait discs. There are no drop shadows. Elevation is tonal: surface containers step up in tone. Motion is Material's default. The one custom transition is ColumnBrowser's column slide, 400ms emphasized-decelerate. + +### States +Pressed and hovered states use Material's state layers. A focus ring is 2px `primary` with a 2px offset, at least 3:1 on every surface. Show a selection with a fill (a chip or the selected pane row) as well as a check mark or a change of word. + +### Imagery +Poet portraits come from Ganjoor and are cropped into a circle on `secondary-container`. The poet's initial sits underneath, so a missing portrait never leaves a hole. There are no other illustrations. + +## Iconography +Material Icons in the filled style, at 24px, tinted with colour roles (see the Icons group). Use the standard Android share glyph wherever there is sharing, because people recognise it before reading the label. The brand mark is the eight-pointed khatam star (see Logos). There is no wordmark: گنجور is set in Naskh. + +## Large screens +Tablets and unfolded foldables use **ColumnBrowser**. Tapping a poet collapses the home grid into a narrow poets column on the far right (portrait with the name underneath). To its left come the poet's books, then the chapters or poems of the open book, each column scrolling on its own. Older columns narrow as new ones open, so the poem stays near the middle, and a top-bar button hides the columns when the reader wants to focus on the poem. Phones are unchanged. diff --git a/design/assets/Icons/README.md b/design/assets/Icons/README.md new file mode 100644 index 0000000..cd3c686 --- /dev/null +++ b/design/assets/Icons/README.md @@ -0,0 +1,16 @@ +# Icons + +The app uses **Material Icons, filled style**: `Icons.Default.*` from Compose for back (auto-mirrored), home, search, settings, share, favourite or favourite-border, check-circle and the keyboard arrows. It also keeps the vector drawables below, which are copies of Material symbols for glyphs Compose's core set lacks. + +All of them are single-ink SVGs drawn in ink `#191c1c` (light `on-surface`). In the app they are tinted at runtime: actions in `on-surface-variant`, the toggled state in `primary`, and the download tick in `downloaded`. When you use these files in ``, swap the fill for the colour you need. + +| File | Material name | Where | +|---|---|---| +| ic-download.svg | download | Downloads in the top bar; download a poet | +| ic-pin.svg | push_pin | Pin mark on a pinned poet, 16px | +| ic-view-grid.svg / ic-view-list.svg | grid_view / view_list | Poets view toggle | +| ic-language.svg | language | UI language switcher | +| ic-share.svg | share | Share a poem or passage | +| ic-lookup.svg | search | Dictionary entry in the selection menu | +| ic-ask.svg | auto_awesome | Ask an assistant | +| ic-pause.svg | pause | Recitation player | diff --git a/design/assets/Icons/ic-ask.svg b/design/assets/Icons/ic-ask.svg new file mode 100644 index 0000000..a1995bc --- /dev/null +++ b/design/assets/Icons/ic-ask.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-download.svg b/design/assets/Icons/ic-download.svg new file mode 100644 index 0000000..1823e8c --- /dev/null +++ b/design/assets/Icons/ic-download.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-language.svg b/design/assets/Icons/ic-language.svg new file mode 100644 index 0000000..883dbe3 --- /dev/null +++ b/design/assets/Icons/ic-language.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-lookup.svg b/design/assets/Icons/ic-lookup.svg new file mode 100644 index 0000000..cafd9d3 --- /dev/null +++ b/design/assets/Icons/ic-lookup.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-pause.svg b/design/assets/Icons/ic-pause.svg new file mode 100644 index 0000000..9f4194a --- /dev/null +++ b/design/assets/Icons/ic-pause.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-pin.svg b/design/assets/Icons/ic-pin.svg new file mode 100644 index 0000000..7ec4ca5 --- /dev/null +++ b/design/assets/Icons/ic-pin.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-share.svg b/design/assets/Icons/ic-share.svg new file mode 100644 index 0000000..afc7ad5 --- /dev/null +++ b/design/assets/Icons/ic-share.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-view-grid.svg b/design/assets/Icons/ic-view-grid.svg new file mode 100644 index 0000000..a62aff9 --- /dev/null +++ b/design/assets/Icons/ic-view-grid.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Icons/ic-view-list.svg b/design/assets/Icons/ic-view-list.svg new file mode 100644 index 0000000..d9eeadf --- /dev/null +++ b/design/assets/Icons/ic-view-list.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Logos/README.md b/design/assets/Logos/README.md new file mode 100644 index 0000000..1458598 --- /dev/null +++ b/design/assets/Logos/README.md @@ -0,0 +1,9 @@ +# Logos + +The mark is an eight-pointed star (khatam) with an eight-pointed star cut from its centre, from Persian tilework. The app has no wordmark: the name گنجور is set in type (Noto Naskh Arabic) wherever it appears. + +- `ganjoor-mark.svg`: the adaptive launcher icon as composed. The star is in paper `#f1e5ce` (sepia `surface`) on a turquoise `#00696e` (light `primary`) square. Use it on any ground. +- `ganjoor-star-mono.svg`: the monochrome launcher layer, black on transparent, used for Android 13 themed icons. It only works on light grounds. +- `ganjoor-launcher-round.png`: the rendered round launcher icon, xxxhdpi. + +Don't recolour the star, add a wordmark or outline it. diff --git a/design/assets/Logos/ganjoor-mark.svg b/design/assets/Logos/ganjoor-mark.svg new file mode 100644 index 0000000..fb3213c --- /dev/null +++ b/design/assets/Logos/ganjoor-mark.svg @@ -0,0 +1 @@ + diff --git a/design/assets/Logos/ganjoor-star-mono.svg b/design/assets/Logos/ganjoor-star-mono.svg new file mode 100644 index 0000000..f942dc0 --- /dev/null +++ b/design/assets/Logos/ganjoor-star-mono.svg @@ -0,0 +1 @@ + diff --git a/design/components/BottomSearchBar/README.md b/design/components/BottomSearchBar/README.md new file mode 100644 index 0000000..86be19b --- /dev/null +++ b/design/components/BottomSearchBar/README.md @@ -0,0 +1,8 @@ +# BottomSearchBar + +The poets search, docked at the *bottom* of the home screen where a one-handed thumb reaches. + +- **Consumer provides:** the query and handlers for change and for "search poems". +- A Surface at tonal elevation 3 (`surface-tonal-3`), `space-12` / `space-8` padding, holding an OutlinedTextField (`radius-xs`, `outline` border, leading search icon) labelled "Search poets". +- Typing filters the poets in place. Once there is a query, a row appears above the field — "Search poems for “…”" with a `primary` search icon — that carries the same words into full-text poem search. +- Rides above the keyboard (ime padding) and the navigation bar. diff --git a/design/components/BottomSearchBar/preview.html b/design/components/BottomSearchBar/preview.html new file mode 100644 index 0000000..8382aae --- /dev/null +++ b/design/components/BottomSearchBar/preview.html @@ -0,0 +1,6 @@ + + +
+ +
+ diff --git a/design/components/Breadcrumbs/README.md b/design/components/Breadcrumbs/README.md new file mode 100644 index 0000000..febd010 --- /dev/null +++ b/design/components/Breadcrumbs/README.md @@ -0,0 +1,9 @@ +# Breadcrumbs + +The poem's path at the top of the reader — poet » book » section » this poem — with every ancestor tappable. + +- **Consumer provides:** the poem's full title and URL; crumbs are split from them. +- `title-medium`. Ancestors are `primary` and open that category; the current poem is `on-surface` and not a link; the ` » ` separators are `on-surface-variant`. +- Wraps onto further lines (FlowRow) instead of truncating — a long Golestan path stays readable. +- If no crumbs can be derived, show the full title as plain `title-medium` text. +- Below it: the recitation player, then the metre line in `body-small` / `on-surface-variant`. diff --git a/design/components/Breadcrumbs/preview.html b/design/components/Breadcrumbs/preview.html new file mode 100644 index 0000000..9d59513 --- /dev/null +++ b/design/components/Breadcrumbs/preview.html @@ -0,0 +1,7 @@ + + +
+ +
مفاعیلن مفاعیلن مفاعیلن مفاعیلن (هزج مثمن سالم)
+
+ diff --git a/design/components/ColumnBrowser/README.md b/design/components/ColumnBrowser/README.md new file mode 100644 index 0000000..b8fc458 --- /dev/null +++ b/design/components/ColumnBrowser/README.md @@ -0,0 +1,58 @@ +# ColumnBrowser + +The layout for tablets and unfolded foldables. A narrow column of poets sits on the right. Beside it is a column for each level of the open book (books › chapters › poems), and the open page fills the rest of the screen on the left. Phones keep today's navigation. Built in `app/src/main/java/com/ganjoor/android/ui/ColumnBrowser.kt`; wireframes are in `design/wireframes/`. + +## Window sizes + +| Width | Layout | +|---|---| +| < 600dp (phones, a folded foldable) | Unchanged: one screen at a time. | +| 600–839dp (small tablets, a book-style foldable open in portrait) | Poets column, the **newest** list column only (its header has a back arrow to the level above), and the page. | +| ≥ 840dp (tablets, an open foldable in landscape) | Poets column, up to three list columns, and the page. | + +Both layouts use the same navigation routes, so folding or unfolding keeps your place. + +## Columns, right to left + +1. **Poets** (`surface-container`, 96dp; 80dp once two or more lists are open or a poem is being read): a portrait disc (56dp, or 44dp when narrow) with the name underneath in `label-medium`. The selected poet gets a `secondary-container` tile and a 2dp `primary` ring around the portrait. The poets appear in the same order as on the home screen, pinned poets first. +2. **List columns** (`surface-container-low`): a poet's books, a book's chapters, a chapter's poems. The header shows the title of what the column lists. Selecting a row replaces every column to its left. Each column keeps its own scroll position. +3. **The page** (`surface`): the usual TopAppBar, then the breadcrumbs, then the content at a centred measure (680dp for a poem, 760dp for a book's cards). + +## Columns give their room to the page + +| Column | Browsing | Reading a poem | +|---|---|---| +| Newest | 224dp: `body-large` titles, poems show their first line, and a chevron where a row opens another column | 168dp, terse | +| One back | 168dp, terse | 132dp, terse | +| Two back | 132dp, terse | 132dp, terse | + +**Terse** means `body-medium` titles wrapped to two lines and cut with an ellipsis, with no first lines and no chevrons. Width changes animate over 300ms. A new column opens out from zero width. + +## Reader view + +- The **hide** button (`menu_open`) is the first action in the page's top bar. It slides the columns away to the right, and the page takes the whole width, still centred. +- While the columns are hidden, a **floating button** (`menu`) at the bottom-right corner brings them back exactly as they were. +- The choice is saved (`columnsHidden` in Settings). + +## Couplets + +When the page is at least 640dp wide, a Right+Left couplet sits on one line: the first half at the start, the second at the end, 32dp apart. When it's narrower, couplets stack as on the phone. Centred verses and prose always keep their own lines. + +## Dictionary + +On large screens, tapping a word opens **WordPanel** on the left instead of the bottom sheet: +- `surface-container-low`, 360dp wide, with a "Dictionary" header, a ✕ button, and the same lookup and couplet actions as the sheet. +- The poem moves over to make room, so nothing being read is covered. +- The tapped word stays highlighted in the verse (`secondary-container` / `on-secondary-container`), on phones too. +- ✕ or Back closes the panel. + +## Loading + +Nothing covers the whole screen while it loads, on phones as well: +- Only the part that is waiting shows a skeleton shaped like its content (`Skeleton.kt`): list rows in a column, portrait discs in the poets column, cards on a book's page, couplets on a poem. +- The real top bar stays visible above the skeleton, so Back already works. +- The skeletons pulse softly, and the content fades in over them when it arrives. + +## Not yet done + +- Lining the page edge up with a foldable's hinge (`FoldingFeature`, Jetpack WindowManager). diff --git a/design/components/Couplet/README.md b/design/components/Couplet/README.md new file mode 100644 index 0000000..bc584d3 --- /dev/null +++ b/design/components/Couplet/README.md @@ -0,0 +1,11 @@ +# Couplet + +One line of poetry: two hemistichs stacked on a phone, the first pushed to the start of the line and the second to the end — the way Ganjoor itself reads. + +- **Consumer provides:** the couplet's verses (each with its position), the reading style from settings (`poem-naskh` or `poem-nastaliq` at the reader's size and weight), and handlers for a tapped word and for the actions. +- Alignment by verse position: first hemistich `start`, second `end`, centred verses `center`, prose (single/paragraph/comment) `justify` so Golestan and Nowruznameh fill the column. +- `space-6` above and below each couplet. Text is `on-surface`. +- Tapping a word opens the dictionary sheet; tapping between words, or the 32px chevron (`touch-compact`, 20px icon, `on-surface-variant`), opens the couplet's actions: **Save this passage**, **Copy**, **Share** as TextButtons. Save keeps the link back to the poem; Copy doesn't. +- Long-press belongs to text selection — never bind actions to it here. +- With summaries on, Ganjoor's couplet summary sits underneath in `body-small` / `on-surface-variant`. +- Nastaliq needs `2.4` leading or its swashes clip the line above; naskh uses `1.8`. diff --git a/design/components/Couplet/preview.html b/design/components/Couplet/preview.html new file mode 100644 index 0000000..afb6099 --- /dev/null +++ b/design/components/Couplet/preview.html @@ -0,0 +1,10 @@ + + +
+

الا یا ایها الساقی ادر کأسا و ناولها

که عشق آسان نمود اول ولی افتاد مشکل‌ها

+
+
+

به بوی نافه‌ای کاخر صبا زان طره بگشاید

ز تاب جعد مشکینش چه خون افتاد در دل‌ها

+
+
+ diff --git a/design/components/Cover/preview.html b/design/components/Cover/preview.html new file mode 100644 index 0000000..7598e86 --- /dev/null +++ b/design/components/Cover/preview.html @@ -0,0 +1,31 @@ + +
+ +

Ganjoor

Persian poetry, set for long reading: tilework turquoise, saffron and paper.

+
diff --git a/design/components/DownloadStatus/README.md b/design/components/DownloadStatus/README.md new file mode 100644 index 0000000..9194600 --- /dev/null +++ b/design/components/DownloadStatus/README.md @@ -0,0 +1,9 @@ +# DownloadStatus + +Fetch a poet's poems, or say they are already here — three states, no menu. + +- **Consumer provides:** the poet's slug; state comes from the download queue and the offline store. +- **Idle:** the download arrow in `on-surface-variant`, tap to start. +- **Downloading:** an 18px (`icon-inline`) progress ring, `space-2` stroke, in `primary`; tap cancels. +- **Saved:** the CheckCircle in `downloaded` green — a tick shape as well as a colour. It sits in a 48px (`touch`) box even though it is not tappable, so ticks and arrows line up down the column. +- Deleting is never offered here; it lives on the Downloads screen next to the sizes. A tap beside a poet's name should never throw their poems away. diff --git a/design/components/DownloadStatus/preview.html b/design/components/DownloadStatus/preview.html new file mode 100644 index 0000000..1a8b9d1 --- /dev/null +++ b/design/components/DownloadStatus/preview.html @@ -0,0 +1,8 @@ + + +
+
Idle
+
Downloading
+
Saved
+
+ diff --git a/design/components/FilterChips/README.md b/design/components/FilterChips/README.md new file mode 100644 index 0000000..17e4245 --- /dev/null +++ b/design/components/FilterChips/README.md @@ -0,0 +1,9 @@ +# FilterChips + +A wrapping row of single-choice Material FilterChips — poet sort order on the home screen, and theme, font, weight and language in the reading settings sheet. + +- **Consumer provides:** the options, the selected one, and a label for each. +- 32px tall, `radius-sm`, `label-large`, `space-8` apart, wrapping (FlowRow). +- Selected: `secondary-container` fill, `on-secondary-container` label and a check mark. Unselected: transparent with a 1px border. +- Exactly one is always selected; these are a choice, not filters that can all be off. +- Language chips label each language in its own script (فارسی · اردو · English), whatever the UI language. diff --git a/design/components/FilterChips/preview.html b/design/components/FilterChips/preview.html new file mode 100644 index 0000000..44e1d70 --- /dev/null +++ b/design/components/FilterChips/preview.html @@ -0,0 +1,7 @@ + + +
+
+
+
+ diff --git a/design/components/PassageActions/README.md b/design/components/PassageActions/README.md new file mode 100644 index 0000000..241c5d1 --- /dev/null +++ b/design/components/PassageActions/README.md @@ -0,0 +1,8 @@ +# PassageActions + +The row of TextButtons under an opened couplet: save the passage, copy it, share it. + +- **Consumer provides:** the passage (excerpt plus the poem's URL and title). +- TextButtons in `label-large`, `space-8` apart. **Save** reads in the content colour until saved, then turns `primary` and says **Saved** — the state is in the word, not only the colour. +- **Share** carries the standard Android share glyph at `icon-inline` (18px): people recognise the shape before the word. +- The same row appears in the word sheet, so a passage can be saved from wherever the reader is. diff --git a/design/components/PassageActions/preview.html b/design/components/PassageActions/preview.html new file mode 100644 index 0000000..7422068 --- /dev/null +++ b/design/components/PassageActions/preview.html @@ -0,0 +1,7 @@ + + +
+
Not saved
+
Saved
+
+ diff --git a/design/components/PoetCard/README.md b/design/components/PoetCard/README.md new file mode 100644 index 0000000..edbfc1d --- /dev/null +++ b/design/components/PoetCard/README.md @@ -0,0 +1,9 @@ +# PoetCard + +One poet in the home grid: a round portrait over their name, in a filled card. + +- **Consumer provides:** the poet (name, portrait URL), whether they are pinned, and click / long-click handlers. +- Filled Card: `surface-container-highest`, `radius-md`, `space-12` padding, `space-8` gap. The grid is adaptive with columns at least `poet-grid-min` (132px), `space-12` gutters. +- The portrait is an `avatar-card` (84px) disc on `secondary-container`; the poet's initial (`headline-medium`, `on-secondary-container`) sits *underneath* the photo so a missing portrait is never a hole. +- Name in `title-medium`, centred, up to two lines. +- Long-press pins the poet; a pinned poet shows the 16px pin (`icon-pin`, `primary`) before the name. The "Pinned" sort puts them first. diff --git a/design/components/PoetCard/preview.html b/design/components/PoetCard/preview.html new file mode 100644 index 0000000..16d3325 --- /dev/null +++ b/design/components/PoetCard/preview.html @@ -0,0 +1,8 @@ + + +
+
ح
حافظ
+
خ
خیام
+
پ
پروین اعتصامی
+
+ diff --git a/design/components/PoetRow/README.md b/design/components/PoetRow/README.md new file mode 100644 index 0000000..78a7c10 --- /dev/null +++ b/design/components/PoetRow/README.md @@ -0,0 +1,8 @@ +# PoetRow + +One poet as a list row, for readers who would rather scan names than faces. + +- **Consumer provides:** the poet, whether they are pinned, and click / long-click handlers. +- Material ListItem: `avatar-row` (44px) portrait disc leading, name in `title-medium`, trailing pin mark plus the **DownloadStatus** for that poet. +- Same pin behaviour as PoetCard: long-press to pin; the pin mark shows only on pinned poets. +- The grid/list choice is a top-bar toggle and is remembered. diff --git a/design/components/PoetRow/preview.html b/design/components/PoetRow/preview.html new file mode 100644 index 0000000..aa81d90 --- /dev/null +++ b/design/components/PoetRow/preview.html @@ -0,0 +1,8 @@ + + +
+
ح
حافظ
+
س
سعدی
+
م
مولانا
+
+ diff --git a/design/components/ReadingSettingsSheet/README.md b/design/components/ReadingSettingsSheet/README.md new file mode 100644 index 0000000..5ffc1b8 --- /dev/null +++ b/design/components/ReadingSettingsSheet/README.md @@ -0,0 +1,9 @@ +# ReadingSettingsSheet + +The Material bottom sheet behind the gear icon: theme, OLED, font, weight, text size with a live preview, language, offline mode and summaries. + +- **Consumer provides:** nothing; it reads and writes the app's settings. +- `surface-container-low` with `radius-xl` top corners; `space-20` side padding, `space-8` between rows, `space-32` at the bottom. It opens half-height and scrolls, inset above the navigation bar. +- Order: Theme chips → OLED toggle → Font chips → Weight chips → Text size slider (14–40, steps of 2) → **live preview** of a hemistich in the chosen reading style → Language chips → Offline mode → Show summaries → AI assistant, About & licences as TextButtons. +- Every group has a **SectionLabel**. Changes apply immediately; there is no Save. +- Changing language recreates the activity. diff --git a/design/components/ReadingSettingsSheet/preview.html b/design/components/ReadingSettingsSheet/preview.html new file mode 100644 index 0000000..f512724 --- /dev/null +++ b/design/components/ReadingSettingsSheet/preview.html @@ -0,0 +1,11 @@ + + +
+
+
پوسته
+
پس‌زمینهٔ سیاه (OLED)
روی پوسته‌های تاریک اعمال می‌شود
+
قلم
+
اندازهٔ متن
+

الا یا ایها الساقی ادر کأسا و ناولها

+
+ diff --git a/design/components/SectionLabel/README.md b/design/components/SectionLabel/README.md new file mode 100644 index 0000000..4fa8043 --- /dev/null +++ b/design/components/SectionLabel/README.md @@ -0,0 +1,6 @@ +# SectionLabel + +The heading over each group in the reading settings sheet. + +- **Consumer provides:** the text. +- `title-small` in `primary`, with `space-12` above it. Not a link and not tappable; it only names the group of chips, slider or toggle beneath. diff --git a/design/components/SectionLabel/preview.html b/design/components/SectionLabel/preview.html new file mode 100644 index 0000000..de40cab --- /dev/null +++ b/design/components/SectionLabel/preview.html @@ -0,0 +1,4 @@ + + +
پوسته
اندازهٔ متن
+ diff --git a/design/components/SettingToggle/README.md b/design/components/SettingToggle/README.md new file mode 100644 index 0000000..8f51457 --- /dev/null +++ b/design/components/SettingToggle/README.md @@ -0,0 +1,8 @@ +# SettingToggle + +A setting with a one-line explanation and a Material Switch. + +- **Consumer provides:** a title, a note and the checked state. +- Title in `body-large` / `on-surface`; the note under it in `body-small` / `on-surface-variant`; the Switch at the end. `space-12` above each toggle. +- The note says what the setting does *and* when it applies ("Applies to the dark themes; saves power on OLED screens"). Put a toggle beside the choice it modifies — OLED sits right under the theme chips. +- Switch on: `primary` track, `on-primary` thumb. Off: `surface-container-highest` track with an `outline` border and thumb. diff --git a/design/components/SettingToggle/preview.html b/design/components/SettingToggle/preview.html new file mode 100644 index 0000000..60edc4c --- /dev/null +++ b/design/components/SettingToggle/preview.html @@ -0,0 +1,7 @@ + + +
+
پس‌زمینهٔ سیاه (OLED)
روی پوسته‌های تاریک اعمال می‌شود و در نمایشگر OLED باتری کمتری می‌برد
+
حالت برون‌خط
فقط از شعرهای ذخیره‌شده بخوان
+
+ diff --git a/design/components/TopAppBar/README.md b/design/components/TopAppBar/README.md new file mode 100644 index 0000000..8d65c3e --- /dev/null +++ b/design/components/TopAppBar/README.md @@ -0,0 +1,10 @@ +# TopAppBar + +The Material 3 small top app bar every screen uses: back arrow, a one-line title, then a row of icon actions. + +- **Consumer provides:** the title (a poem title, a category, or the app name گنجور), an optional up action, and the actions for that screen. +- Title in `title-large`, ellipsized to one line. The navigation icon is `on-surface`; action icons are `on-surface-variant`, 24px in a 48px `touch` target. +- A toggled action (a saved bookmark heart) turns `primary`, so the state is visible without reading the label. +- Action order on the poem screen: Home, Share, Bookmark, Reading settings. On the poets screen: view toggle, language, bookmarks, downloads, reading settings. Reading settings is always last, so it is always in the same place. +- The arrow is auto-mirrored: in the RTL layout it points right. +- Don't add a menu overflow; every action has its own icon and content description. diff --git a/design/components/TopAppBar/preview.html b/design/components/TopAppBar/preview.html new file mode 100644 index 0000000..b3e0a37 --- /dev/null +++ b/design/components/TopAppBar/preview.html @@ -0,0 +1,9 @@ + + +
+
غزل شمارهٔ ۳
+
+
+
گنجور
+
+ diff --git a/design/components/bundle.css b/design/components/bundle.css new file mode 100644 index 0000000..688dd83 --- /dev/null +++ b/design/components/bundle.css @@ -0,0 +1,158 @@ +/* Ganjoor — static renditions of the app's Compose components. Values come from tokens.css. */ +.gj { font-family: var(--font-naskh); color: var(--on-surface); background: var(--surface); } +.gj[lang="en"] { font-family: var(--font-libron); } +.gj *, .gj *::before, .gj *::after { box-sizing: border-box; } +.gj-icon { width: var(--icon); height: var(--icon); fill: currentColor; flex: none; display: block; } +.gj-icon-btn { width: var(--touch); height: var(--touch); border: 0; background: none; color: var(--on-surface-variant); border-radius: var(--radius-full); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; } +.gj-icon-btn:hover { background: color-mix(in srgb, var(--on-surface-variant) 8%, transparent); } +.gj-icon-btn:focus-visible, .gj-text-btn:focus-visible, .gj-chip:focus-visible, .gj-card:focus-visible, .gj-row:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } + +/* Top app bar: 64px, surface, title-large; nav icon on-surface, actions on-surface-variant. */ +.gj-topbar { height: 64px; display: flex; align-items: center; gap: var(--space-4); padding: 0 var(--space-4); background: var(--surface); } +.gj-topbar .gj-nav { color: var(--on-surface); } +.gj-topbar-title { flex: 1; min-width: 0; font-size: 22px; line-height: 28px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-inline-start: var(--space-4); } +.gj-topbar-actions { display: flex; } +.gj-topbar .gj-on { color: var(--primary); } + +/* Text button: primary label-large, pill, 40px. */ +.gj-text-btn { height: 40px; padding: 0 12px; border: 0; background: none; border-radius: var(--radius-full); color: var(--primary); font: 500 14px/20px var(--font-naskh); letter-spacing: 0.1px; display: inline-flex; align-items: center; gap: var(--space-4); cursor: pointer; } +.gj[lang="en"] .gj-text-btn { font-family: var(--font-libron); } +.gj-text-btn .gj-icon { width: var(--icon-inline); height: var(--icon-inline); } +.gj-text-btn.is-neutral { color: var(--on-surface); } + +/* Breadcrumbs: title-medium; ancestors primary and tappable, current on-surface, » separators muted. */ +.gj-crumbs { display: flex; flex-wrap: wrap; align-items: center; font-size: 16px; line-height: 24px; font-weight: 500; } +.gj-crumbs a { color: var(--primary); text-decoration: none; cursor: pointer; } +.gj-crumbs .gj-sep { color: var(--on-surface-variant); white-space: pre; } +.gj-crumbs .gj-here { color: var(--on-surface); } +.gj-metre { font-size: 12px; line-height: 16px; letter-spacing: .4px; color: var(--on-surface-variant); } + +/* Couplet: two hemistichs stacked; first to the start, second to the end. */ +.gj-couplet { padding: var(--space-6) 0; } +.gj-verse { font-family: var(--font-naskh); font-size: 22px; line-height: 1.8; color: var(--on-surface); margin: 0; } +.gj-couplet.is-nastaliq .gj-verse { font-family: var(--font-nastaliq); line-height: 2.4; } +.gj-verse.is-first { text-align: start; } +.gj-verse.is-second { text-align: end; } +.gj-verse.is-centered { text-align: center; } +.gj-verse.is-prose { text-align: justify; } +.gj-couplet-summary { font-size: 12px; line-height: 16px; letter-spacing: .4px; color: var(--on-surface-variant); padding-top: var(--space-4); } +.gj-couplet-toggle { display: flex; justify-content: flex-end; } +.gj-couplet-toggle .gj-icon-btn { width: var(--touch-compact); height: var(--touch-compact); } +.gj-couplet-toggle .gj-icon { width: var(--icon-small); height: var(--icon-small); } +.gj-actions { display: flex; flex-wrap: wrap; gap: var(--space-8); } + +/* Filter chips: 32px, radius-sm; selected = secondary-container with a check. */ +.gj-chips { display: flex; flex-wrap: wrap; gap: var(--space-8); } +.gj-chip { height: 32px; padding: 0 16px; border-radius: var(--radius-sm); border: 1px solid var(--outline); background: transparent; color: var(--on-surface-variant); font: 500 14px/20px var(--font-naskh); letter-spacing: .1px; display: inline-flex; align-items: center; gap: var(--space-8); cursor: pointer; } +.gj[lang="en"] .gj-chip { font-family: var(--font-libron); } +.gj-chip[aria-pressed="true"] { background: var(--secondary-container); color: var(--on-secondary-container); border-color: transparent; padding-inline-start: 8px; } +.gj-chip .gj-icon { width: var(--icon-inline); height: var(--icon-inline); } + +/* Poet portrait disc: secondary-container with the initial underneath the photo. */ +.gj-portrait { border-radius: var(--radius-full); background: var(--secondary-container); color: var(--on-secondary-container); display: flex; align-items: center; justify-content: center; overflow: hidden; flex: none; position: relative; } +.gj-portrait img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } +.gj-portrait.is-row { width: var(--avatar-row); height: var(--avatar-row); font-size: 16px; } +.gj-portrait.is-card { width: var(--avatar-card); height: var(--avatar-card); font-size: 28px; line-height: 36px; } + +/* Poet card: filled Card, radius-md, surface-container-highest, 12 padding, 8 gap. */ +.gj-poet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--poet-grid-min), 1fr)); gap: var(--space-12); } +.gj-card { background: var(--surface-container-highest); color: var(--on-surface); border-radius: var(--radius-md); padding: var(--space-12); display: flex; flex-direction: column; align-items: center; gap: var(--space-8); cursor: pointer; } +.gj-name { font-size: 16px; line-height: 24px; font-weight: 500; letter-spacing: .15px; text-align: center; display: flex; align-items: center; gap: var(--space-4); } +.gj-pin { width: var(--icon-pin); height: var(--icon-pin); color: var(--primary); fill: currentColor; flex: none; } + +/* List row: Material ListItem, 16 side padding, 56–72 tall. */ +.gj-row { display: flex; align-items: center; gap: var(--space-16); min-height: 72px; padding: var(--space-8) var(--space-16); background: var(--surface); cursor: pointer; } +.gj-row-text { flex: 1; min-width: 0; } +.gj-row-trailing { display: flex; align-items: center; gap: var(--space-4); } +.gj-row-support { font-size: 14px; line-height: 20px; letter-spacing: .25px; color: var(--on-surface-variant); } + +/* Download state, all in a 48px box so ticks and arrows line up. */ +.gj-dl { width: var(--touch); height: var(--touch); display: inline-flex; align-items: center; justify-content: center; } +.gj-dl.is-saved { color: var(--downloaded); } +.gj-dl.is-idle { color: var(--on-surface-variant); } +.gj-ring { width: var(--icon-inline); height: var(--icon-inline); border-radius: 50%; border: var(--space-2) solid var(--surface-container-highest); border-top-color: var(--primary); } + +/* Settings sheet pieces. */ +.gj-sheet { background: var(--surface-container-low); border-radius: var(--radius-xl) var(--radius-xl) 0 0; padding: var(--space-8) var(--space-20) var(--space-32); display: flex; flex-direction: column; gap: var(--space-8); } +.gj-handle { width: 32px; height: 4px; border-radius: var(--radius-full); background: var(--on-surface-variant); opacity: .4; margin: var(--space-8) auto var(--space-8); } +.gj-label { font-size: 14px; line-height: 20px; font-weight: 500; letter-spacing: .1px; color: var(--primary); padding-top: var(--space-12); } +.gj-toggle { display: flex; align-items: center; gap: var(--space-16); padding-top: var(--space-12); } +.gj-toggle-text { flex: 1; } +.gj-toggle-title { font-size: 16px; line-height: 24px; color: var(--on-surface); } +.gj-toggle-note { font-size: 12px; line-height: 16px; letter-spacing: .4px; color: var(--on-surface-variant); } +.gj-switch { width: 52px; height: 32px; border-radius: var(--radius-full); border: 2px solid var(--outline); background: var(--surface-container-highest); position: relative; flex: none; cursor: pointer; padding: 0; } +.gj-switch::after { content: ""; position: absolute; top: 50%; inset-inline-start: 6px; width: 16px; height: 16px; margin-top: -8px; border-radius: 50%; background: var(--outline); } +.gj-switch[aria-checked="true"] { background: var(--primary); border-color: var(--primary); } +.gj-switch[aria-checked="true"]::after { inset-inline-start: auto; inset-inline-end: 2px; width: 24px; height: 24px; margin-top: -12px; background: var(--on-primary); } + +/* Bottom search bar: tonal surface, outlined field. */ +.gj-searchbar { background: var(--surface-tonal-3); padding: var(--space-8) var(--space-12); } +.gj-field { height: 56px; border: 1px solid var(--outline); border-radius: var(--radius-xs); display: flex; align-items: center; gap: var(--space-16); padding: 0 var(--space-12); color: var(--on-surface-variant); font-size: 16px; line-height: 24px; letter-spacing: .5px; } +.gj-search-poems { display: flex; align-items: center; gap: var(--space-16); min-height: 56px; padding: 0 var(--space-16); color: var(--on-surface); } +.gj-search-poems .gj-icon { color: var(--primary); } + +.gj-divider { height: 1px; background: var(--outline-variant); border: 0; margin: 0; } +.gj-ai-note { font-size: 11px; line-height: 16px; font-weight: 500; letter-spacing: .5px; color: var(--primary); padding-top: var(--space-12); } +.gj-summary { font-size: 14px; line-height: 20px; letter-spacing: .25px; color: var(--on-surface-variant); } + +/* ── Large screens: ColumnBrowser (tablets, unfolded foldables; width ≥ 600) ── + Column browser in RTL: poets rail on the far right, then books, then chapters / poems, + and the open poem fills what is left on the left. */ +.gj-ts { display: flex; height: 100%; overflow: hidden; background: var(--surface); } +.gj-ts-home { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.gj-ts.is-split .gj-ts-home { display: none; } +.gj-ts-scroll { overflow-y: auto; min-height: 0; overscroll-behavior: contain; } +.gj-ts-cols { display: none; flex: none; height: 100%; } +.gj-ts.is-split .gj-ts-cols { display: flex; } +.gj-ts-col { flex: none; width: 224px; display: flex; flex-direction: column; min-height: 0; border-inline-end: 1px solid var(--outline-variant); background: var(--surface-container-low); transition: width 300ms cubic-bezier(.2,0,0,1); } +.gj-ts-col.is-new { animation: gj-ts-in 400ms cubic-bezier(.05,.7,.1,1) both; } +/* Columns narrow as you go deeper: the newest list is widest, older ones give their room to the poem. */ +.gj-ts-col[data-dist="1"] { width: 168px; } +.gj-ts-col[data-dist="2"] { width: 132px; } +.gj-ts-col[data-dist="1"] .gj-ts-item, .gj-ts-col[data-dist="2"] .gj-ts-item { font-size: 14px; line-height: 20px; padding: var(--space-8); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } +.gj-ts-col[data-dist="1"] .gj-ts-item small, .gj-ts-col[data-dist="2"] .gj-ts-item small, .gj-ts-col[data-dist="1"] .gj-ts-item::after, .gj-ts-col[data-dist="2"] .gj-ts-item::after { display: none; } +.gj-ts-col[data-dist="1"] .gj-ts-colhead, .gj-ts-col[data-dist="2"] .gj-ts-colhead { padding: 0 var(--space-8); } +.gj-ts-col[data-dist="1"] .gj-ts-colhead b, .gj-ts-col[data-dist="2"] .gj-ts-colhead b { font-size: 14px; line-height: 20px; } +.gj-ts-col.is-rail { width: 96px; background: var(--surface-container); } +.gj-ts.is-deep .gj-ts-col.is-rail { width: 80px; } +.gj-ts.is-deep .gj-ts-poet .gj-portrait { width: 44px; height: 44px; font-size: 18px; } +.gj-ts-poet { overflow-wrap: anywhere; } +@keyframes gj-ts-in { from { opacity: 0; transform: translateX(-32px); } } +.gj-ts-col.is-rail.is-arriving { animation-name: gj-ts-rail; } +@keyframes gj-ts-rail { from { width: 100vw; } } +.gj-ts-colhead { flex: none; min-height: 56px; display: flex; flex-direction: column; justify-content: center; padding: 0 var(--space-16); border-bottom: 1px solid var(--outline-variant); } +.gj-ts-colhead small { color: var(--on-surface-variant); font: 500 11px/16px var(--font-naskh); letter-spacing: .5px; } +.gj-ts-colhead b { color: var(--on-surface); font: 500 16px/24px var(--font-naskh); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.gj-ts-list { padding: var(--space-8); flex: 1; } + +/* Poets rail: portrait with the name under it. */ +.gj-ts-poet { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); width: 100%; padding: var(--space-8) var(--space-4); border: 0; background: none; border-radius: var(--radius-md); color: var(--on-surface); font: 500 12px/16px var(--font-naskh); letter-spacing: .5px; text-align: center; cursor: pointer; } +.gj-ts-poet .gj-portrait { width: 56px; height: 56px; font-size: 22px; } +.gj-ts-poet[aria-current="true"] { background: var(--secondary-container); color: var(--on-secondary-container); } +.gj-ts-poet[aria-current="true"] .gj-portrait { outline: 2px solid var(--primary); outline-offset: 2px; } + +/* Book / chapter / poem rows. */ +.gj-ts-item { display: block; width: 100%; min-height: 48px; padding: var(--space-8) var(--space-12); border: 0; background: none; color: var(--on-surface); font: 400 16px/24px var(--font-naskh); text-align: start; cursor: pointer; border-radius: var(--radius-md); } +.gj-ts-item small { display: block; color: var(--on-surface-variant); font-size: 12px; line-height: 16px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.gj-ts-item:hover, .gj-ts-poet:hover { background: color-mix(in srgb, var(--on-surface) 6%, transparent); } +.gj-ts-item[aria-current="true"] { background: var(--secondary-container); color: var(--on-secondary-container); } +.gj-ts-item[aria-current="true"] small { color: var(--on-secondary-container); } +.gj-ts-item.has-kids::after { content: "‹"; float: inline-end; color: var(--on-surface-variant); } + +/* Focus mode: the columns fold away and the poem takes the whole width, still centred. */ +.gj-ts.is-focus .gj-ts-cols { display: none; } +.gj-ts-toggle[aria-pressed="false"] { color: var(--primary); } + +/* The open work. */ +.gj-ts-main { flex: 1; min-width: 0; display: flex; flex-direction: column; animation: gj-ts-in 400ms cubic-bezier(.05,.7,.1,1) both; } +.gj-ts-content { flex: 1; padding: var(--space-8) var(--space-32) var(--space-32); } +.gj-ts-measure { max-width: 680px; margin-inline: auto; } +.gj-ts-main { container-type: inline-size; } +/* Hemistichs side by side only when the poem column has room; otherwise they stack as on a phone. */ +.gj-ts .gj-couplet.is-wide .gj-verse.is-first { text-align: start; } +.gj-ts .gj-couplet.is-wide .gj-verse.is-second { text-align: end; } +@container (min-width: 640px) { + .gj-ts .gj-couplet.is-wide { display: grid; grid-template-columns: 1fr 1fr; column-gap: var(--space-32); align-items: baseline; } +} +.gj-ts-books { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-12); margin-top: var(--space-16); } +.gj-ts-books .gj-card { align-items: flex-start; text-align: start; } diff --git a/design/tokens.json b/design/tokens.json new file mode 100644 index 0000000..e6c296a --- /dev/null +++ b/design/tokens.json @@ -0,0 +1,859 @@ +{ + "name": "Ganjoor", + "version": 1, + "meta": { + "source": "github", + "repo": "anas-rashid/ganjoorandroid", + "ref": "main@50bbe0b", + "package": "app", + "paths": { + "tokens": [ + "app/src/main/java/com/ganjoor/android/ui/theme/Theme.kt", + "app/src/main/java/com/ganjoor/android/ui/theme/Type.kt", + "app/src/main/res/values/colors.xml" + ], + "fonts": [ + "app/src/main/res/font/" + ], + "assets": [ + "app/src/main/res/drawable/", + "app/src/main/res/mipmap-xxxhdpi/", + "fastlane/metadata/android/fa/images/phoneScreenshots/" + ], + "docs": [ + "README.md" + ] + }, + "synced": "2026-10-07" + }, + "color": { + "themes": [ + { + "id": "light", + "name": "Light" + }, + { + "id": "dark", + "name": "Dark" + }, + { + "id": "sepia", + "name": "Sepia" + }, + { + "id": "sepia-night", + "name": "Sepia night" + }, + { + "id": "dark-oled", + "name": "Dark · OLED black" + }, + { + "id": "sepia-night-oled", + "name": "Sepia night · OLED black" + } + ], + "tokens": [ + { + "name": "primary", + "value": { + "light": "#00696e", + "dark": "#80d4da", + "sepia": "#7a4e24", + "sepia-night": "#d9a96c", + "dark-oled": "#80d4da", + "sepia-night-oled": "#d9a96c" + }, + "usage": "Turquoise (sepia: walnut). Tappable crumbs in breadcrumbs, section labels in the settings sheet, the pin mark, a saved bookmark heart, TextButton labels, \"AI-generated\" note. 6.3:1+ on surface in every theme." + }, + { + "name": "on-primary", + "value": { + "light": "#ffffff", + "dark": "#003739", + "sepia": "#ffffff", + "sepia-night": "#3a2510", + "dark-oled": "#003739", + "sepia-night-oled": "#3a2510" + }, + "usage": "Text and icons on a primary fill: Switch thumb when on, filled Button labels." + }, + { + "name": "primary-container", + "value": { + "light": "#9cf1f6", + "dark": "#004f53", + "sepia": "#e8d3b5", + "sepia-night": "#53391c", + "dark-oled": "#004f53", + "sepia-night-oled": "#53391c" + }, + "usage": "Tonal turquoise fill for emphasised containers (Material defaults: filled-tonal surfaces). Rarely used directly." + }, + { + "name": "on-primary-container", + "value": { + "light": "#002021", + "dark": "#9cf1f6", + "sepia": "#2b1700", + "sepia-night": "#f5debe", + "dark-oled": "#9cf1f6", + "sepia-night-oled": "#f5debe" + }, + "usage": "Text on primary-container." + }, + { + "name": "secondary", + "value": { + "light": "#8b5000", + "dark": "#ffb873", + "sepia": "#6b5b3e", + "sepia-night": "#cfbe98", + "dark-oled": "#ffb873", + "sepia-night-oled": "#cfbe98" + }, + "usage": "Saffron accent (sepia: olive-brown). The tilework counterpart to primary; reserve for accents, never body text." + }, + { + "name": "on-secondary", + "value": { + "light": "#ffffff", + "dark": "#4a2800", + "sepia": "#ffffff", + "sepia-night": "#362c14", + "dark-oled": "#4a2800", + "sepia-night-oled": "#362c14" + }, + "usage": "Text on a secondary fill." + }, + { + "name": "secondary-container", + "value": { + "light": "#ffdcbe", + "dark": "#693c00", + "sepia": "#eadfc4", + "sepia-night": "#4d4228", + "dark-oled": "#693c00", + "sepia-night-oled": "#4d4228" + }, + "usage": "Peach fill: a selected FilterChip, and the disc behind a poet portrait (shows the initial when no portrait exists)." + }, + { + "name": "on-secondary-container", + "value": { + "light": "#2c1600", + "dark": "#ffdcbe", + "sepia": "#231a05", + "sepia-night": "#ebdcb7", + "dark-oled": "#ffdcbe", + "sepia-night-oled": "#ebdcb7" + }, + "usage": "Text and check-mark on secondary-container: selected chip label, poet initial. 7.2:1+ in every theme." + }, + { + "name": "background", + "value": { + "light": "#fafdfc", + "dark": "#191c1c", + "sepia": "#f6ecd9", + "sepia-night": "#1c1710", + "dark-oled": "#000000", + "sepia-night-oled": "#000000" + }, + "usage": "Window and splash background (res/values/colors.xml window_background mirrors light and dark)." + }, + { + "name": "on-background", + "value": { + "light": "#191c1c", + "dark": "#e0e3e2", + "sepia": "#3a2e1e", + "sepia-night": "#e6dac4", + "dark-oled": "#e0e3e2", + "sepia-night-oled": "#e6dac4" + }, + "usage": "Text on background." + }, + { + "name": "surface", + "value": { + "light": "#fafdfc", + "dark": "#191c1c", + "sepia": "#f1e5ce", + "sepia-night": "#241d14", + "dark-oled": "#000000", + "sepia-night-oled": "#000000" + }, + "usage": "Page ground behind lists, the reader column, top app bar and sheets." + }, + { + "name": "on-surface", + "value": { + "light": "#191c1c", + "dark": "#e0e3e2", + "sepia": "#3a2e1e", + "sepia-night": "#e6dac4", + "dark-oled": "#e0e3e2", + "sepia-night-oled": "#e6dac4" + }, + "usage": "Primary text on surface: poem verses, list headlines, the current breadcrumb. 10.6:1+ in every theme." + }, + { + "name": "surface-variant", + "value": { + "light": "#dae4e4", + "dark": "#3f4949", + "sepia": "#e5d8bc", + "sepia-night": "#3b3225", + "dark-oled": "#111515", + "sepia-night-oled": "#0f0c07" + }, + "usage": "Muted fill for variant surfaces (Material defaults: slider inactive track, chip disabled)." + }, + { + "name": "on-surface-variant", + "value": { + "light": "#3f4949", + "dark": "#bec8c8", + "sepia": "#5a4c35", + "sepia-night": "#ccbfa6", + "dark-oled": "#bec8c8", + "sepia-night-oled": "#ccbfa6" + }, + "usage": "Secondary text and icons: metre line, couplet summaries, setting notes, \"»\" separators, action icons. 5.6:1+ on surface and surface-container-highest in every theme." + }, + { + "name": "surface-bright", + "value": { + "light": "#fafdfc", + "dark": "#3f4242", + "sepia": "#f8f0e0", + "sepia-night": "#433a2d", + "dark-oled": "#161717", + "sepia-night-oled": "#17130d" + }, + "usage": "Brightest surface tone; OLED keeps it 46% of the way to black." + }, + { + "name": "surface-dim", + "value": { + "light": "#d9dddc", + "dark": "#191c1c", + "sepia": "#dbd0b8", + "sepia-night": "#1c1710", + "dark-oled": "#000000", + "sepia-night-oled": "#000000" + }, + "usage": "Dimmest surface tone." + }, + { + "name": "surface-container-lowest", + "value": { + "light": "#ffffff", + "dark": "#141616", + "sepia": "#fffaf0", + "sepia-night": "#17120c", + "dark-oled": "#000000", + "sepia-night-oled": "#000000" + }, + "usage": "Lowest container tone." + }, + { + "name": "surface-container-low", + "value": { + "light": "#f3f7f6", + "dark": "#212424", + "sepia": "#f2e8d4", + "sepia-night": "#241d14", + "dark-oled": "#000000", + "sepia-night-oled": "#000000" + }, + "usage": "Bottom sheet and elevated card ground (Material default for ModalBottomSheet)." + }, + { + "name": "surface-container", + "value": { + "light": "#eef1f0", + "dark": "#252828", + "sepia": "#ece1ca", + "sepia-night": "#2a2218", + "dark-oled": "#010101", + "sepia-night-oled": "#010101" + }, + "usage": "Default container tone: navigation bar, menus." + }, + { + "name": "surface-container-high", + "value": { + "light": "#e8eceb", + "dark": "#2f3333", + "sepia": "#e6dac0", + "sepia-night": "#352c21", + "dark-oled": "#040505", + "sepia-night-oled": "#050402" + }, + "usage": "Dialogs and dropdown menus." + }, + { + "name": "surface-container-highest", + "value": { + "light": "#e2e6e5", + "dark": "#3a3e3e", + "sepia": "#e0d3b6", + "sepia-night": "#40372b", + "dark-oled": "#0d0f0f", + "sepia-night-oled": "#100c08" + }, + "usage": "Filled Card ground: poet cards in the grid; Switch track when off." + }, + { + "name": "outline", + "value": { + "light": "#6f7979", + "dark": "#899393", + "sepia": "#8c7a5c", + "sepia-night": "#968a72", + "dark-oled": "#899393", + "sepia-night-oled": "#968a72" + }, + "usage": "1px borders: OutlinedTextField, unselected FilterChip outline in focus, Switch border when off. Sepia is 3.3:1 on surface, still above the 3:1 floor for control borders." + }, + { + "name": "outline-variant", + "value": { + "light": "#cac4d0", + "dark": "#49454f", + "sepia": "#cac4d0", + "sepia-night": "#49454f", + "dark-oled": "#49454f", + "sepia-night-oled": "#49454f" + }, + "usage": "Material 3 default, not overridden by any scheme (a faintly purple grey): HorizontalDivider and the unselected FilterChip border. Decorative only; never the sole boundary of a control." + }, + { + "name": "error", + "value": { + "light": "#b3261e", + "dark": "#f2b8b5", + "sepia": "#b3261e", + "sepia-night": "#f2b8b5", + "dark-oled": "#f2b8b5", + "sepia-night-oled": "#f2b8b5" + }, + "usage": "Material 3 default, not overridden. Load-failure and assistant error text on surface." + }, + { + "name": "on-error", + "value": { + "light": "#ffffff", + "dark": "#601410", + "sepia": "#ffffff", + "sepia-night": "#601410", + "dark-oled": "#601410", + "sepia-night-oled": "#601410" + }, + "usage": "Material 3 default. Text on an error fill." + }, + { + "name": "downloaded", + "value": { + "light": "#2e7d32", + "dark": "#81c784", + "sepia": "#2e7d32", + "sepia-night": "#81c784", + "dark-oled": "#81c784", + "sepia-night-oled": "#81c784" + }, + "usage": "ColorScheme.downloaded in Theme.kt: the CheckCircle beside a poet already saved on the device. Picked by surface luminance (<0.5 → light green). Always paired with the tick shape, never colour alone. 4.1:1+ on surface in every theme." + }, + { + "name": "surface-tonal-3", + "value": { + "light": "#e5f1f0", + "dark": "#212b2c", + "sepia": "#e7d9c0", + "sepia-night": "#33291b", + "dark-oled": "#0b1112", + "sepia-night-oled": "#120e09" + }, + "usage": "Derived, not declared: Material 3 tonal elevation 3dp (primary at 8.2% over surface). The bottom search bar's Surface on the poets screen." + } + ] + }, + "type": { + "fonts": [ + { + "family": "Noto Naskh Arabic", + "file": "fonts/NotoNaskhArabic.ttf", + "weight": "400 700", + "style": "normal" + }, + { + "family": "Noto Nastaliq Urdu", + "file": "fonts/NotoNastaliqUrdu.ttf", + "weight": "400 700", + "style": "normal" + }, + { + "family": "Libron", + "file": "fonts/Libron-Regular.ttf", + "weight": "400", + "style": "normal" + }, + { + "family": "Libron", + "file": "fonts/Libron-Bold.ttf", + "weight": "700", + "style": "normal" + } + ], + "families": { + "naskh": "\"Noto Naskh Arabic\", \"Noto Naskh\", serif", + "nastaliq": "\"Noto Nastaliq Urdu\", \"Noto Naskh Arabic\", serif", + "libron": "Libron, Georgia, \"Times New Roman\", serif" + }, + "groups": [ + { + "name": "Reading", + "family": "naskh", + "styles": [ + { + "name": "poem-naskh", + "family": "naskh", + "fontSize": "22px", + "lineHeight": 1.8, + "fontWeight": 400, + "sample": "الا یا ایها الساقی ادر کأسا و ناولها", + "usage": "The poem itself, default font. Size 14–40px in 2px steps (default 22); weight Regular / Medium / Semibold / Bold chosen by the reader; leading always 1.8× the size." + }, + { + "name": "poem-nastaliq", + "family": "nastaliq", + "fontSize": "22px", + "lineHeight": 2.4, + "fontWeight": 400, + "sample": "الا یا ایها الساقی ادر کأسا و ناولها", + "usage": "The poem when the reader picks nastaliq. Leading 2.4× — nastaliq stacks diagonally and has deep descenders, so tighter leading clips the swashes. Same size and weight range as naskh." + } + ] + }, + { + "name": "Interface — Persian & Urdu (Naskh)", + "family": "naskh", + "styles": [ + { + "name": "display-large", + "fontSize": "57px", + "lineHeight": "64px", + "fontWeight": 400, + "letterSpacing": "-0.25px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "display-medium", + "fontSize": "45px", + "lineHeight": "52px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "display-small", + "fontSize": "36px", + "lineHeight": "44px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "headline-large", + "fontSize": "32px", + "lineHeight": "40px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "headline-medium", + "fontSize": "28px", + "lineHeight": "36px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "A poet's initial inside the 84px portrait disc when no portrait loads.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "headline-small", + "fontSize": "24px", + "lineHeight": "32px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "title-large", + "fontSize": "22px", + "lineHeight": "28px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Top app bar titles (poem title, app name گنجور).", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "title-medium", + "fontSize": "16px", + "lineHeight": "24px", + "fontWeight": 500, + "letterSpacing": "0.15px", + "usage": "Poet names on cards and rows; breadcrumbs.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "title-small", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 500, + "letterSpacing": "0.1px", + "usage": "Section labels in the reading settings sheet, in primary.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "body-large", + "fontSize": "16px", + "lineHeight": "24px", + "fontWeight": 400, + "letterSpacing": "0.5px", + "usage": "List item headlines and setting titles (Material default text).", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "body-medium", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 400, + "letterSpacing": "0.25px", + "usage": "Poem summary, list supporting text, empty states.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "body-small", + "fontSize": "12px", + "lineHeight": "16px", + "fontWeight": 400, + "letterSpacing": "0.4px", + "usage": "Secondary notes in on-surface-variant: metre, couplet summaries, setting notes, source line, pin hint.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "label-large", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 500, + "letterSpacing": "0.1px", + "usage": "Buttons, TextButtons and chip labels.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "label-medium", + "fontSize": "12px", + "lineHeight": "16px", + "fontWeight": 500, + "letterSpacing": "0.5px", + "usage": "Small badges and captions.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + }, + { + "name": "label-small", + "fontSize": "11px", + "lineHeight": "16px", + "fontWeight": 500, + "letterSpacing": "0.5px", + "usage": "The \"AI-generated by Ganjoor\" note above a summary, in primary.", + "sample": "گنجور — خوانندهٔ شعر پارسی" + } + ] + }, + { + "name": "Interface — English (Libron)", + "family": "libron", + "styles": [ + { + "name": "en-display-large", + "fontSize": "57px", + "lineHeight": "64px", + "fontWeight": 400, + "letterSpacing": "-0.25px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-display-medium", + "fontSize": "45px", + "lineHeight": "52px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-display-small", + "fontSize": "36px", + "lineHeight": "44px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-headline-large", + "fontSize": "32px", + "lineHeight": "40px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-headline-medium", + "fontSize": "28px", + "lineHeight": "36px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "A poet's initial inside the 84px portrait disc when no portrait loads.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-headline-small", + "fontSize": "24px", + "lineHeight": "32px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Material 3 default scale; carried for completeness, not used on any current screen.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-title-large", + "fontSize": "22px", + "lineHeight": "28px", + "fontWeight": 400, + "letterSpacing": "0px", + "usage": "Top app bar titles (poem title, app name گنجور).", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-title-medium", + "fontSize": "16px", + "lineHeight": "24px", + "fontWeight": 500, + "letterSpacing": "0.15px", + "usage": "Poet names on cards and rows; breadcrumbs.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-title-small", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 500, + "letterSpacing": "0.1px", + "usage": "Section labels in the reading settings sheet, in primary.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-body-large", + "fontSize": "16px", + "lineHeight": "24px", + "fontWeight": 400, + "letterSpacing": "0.5px", + "usage": "List item headlines and setting titles (Material default text).", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-body-medium", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 400, + "letterSpacing": "0.25px", + "usage": "Poem summary, list supporting text, empty states.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-body-small", + "fontSize": "12px", + "lineHeight": "16px", + "fontWeight": 400, + "letterSpacing": "0.4px", + "usage": "Secondary notes in on-surface-variant: metre, couplet summaries, setting notes, source line, pin hint.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-label-large", + "fontSize": "14px", + "lineHeight": "20px", + "fontWeight": 500, + "letterSpacing": "0.1px", + "usage": "Buttons, TextButtons and chip labels.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-label-medium", + "fontSize": "12px", + "lineHeight": "16px", + "fontWeight": 500, + "letterSpacing": "0.5px", + "usage": "Small badges and captions.", + "sample": "Search poems for “Hafez”" + }, + { + "name": "en-label-small", + "fontSize": "11px", + "lineHeight": "16px", + "fontWeight": 500, + "letterSpacing": "0.5px", + "usage": "The \"AI-generated by Ganjoor\" note above a summary, in primary.", + "sample": "Search poems for “Hafez”" + } + ] + } + ] + }, + "spacing": { + "note": "Compose dp values as written in the screens; 1dp = 1px here.", + "tokens": [ + { + "name": "space-2", + "value": "2px", + "usage": "Hairline gaps: CircularProgressIndicator stroke width." + }, + { + "name": "space-4", + "value": "4px", + "usage": "Icon-to-text gap (pin mark beside a name), header bottom padding, summary top padding." + }, + { + "name": "space-6", + "value": "6px", + "usage": "Vertical padding around each couplet." + }, + { + "name": "space-8", + "value": "8px", + "usage": "The default gap: chip spacing, settings sheet rows, card internals, top list padding." + }, + { + "name": "space-12", + "value": "12px", + "usage": "Poet grid gutters and screen side padding on lists; poet card padding; settings toggle top margin." + }, + { + "name": "space-16", + "value": "16px", + "usage": "List bottom padding; empty-state padding; gap before the source line." + }, + { + "name": "space-20", + "value": "20px", + "usage": "Reader column side padding; settings sheet side padding." + }, + { + "name": "space-24", + "value": "24px", + "usage": "Space above the summary and the previous/next poem row." + }, + { + "name": "space-32", + "value": "32px", + "usage": "Bottom padding of the reader and the settings sheet." + } + ] + }, + "radius": { + "note": "The app sets no shapes of its own; these are the Material 3 shape scale the components use.", + "tokens": [ + { + "name": "radius-xs", + "value": "4px", + "usage": "OutlinedTextField (Material extraSmall)." + }, + { + "name": "radius-sm", + "value": "8px", + "usage": "FilterChip (Material small)." + }, + { + "name": "radius-md", + "value": "12px", + "usage": "Card — poet cards in the grid (Material medium)." + }, + { + "name": "radius-lg", + "value": "16px", + "usage": "Material large; unused today." + }, + { + "name": "radius-xl", + "value": "28px", + "usage": "Top corners of ModalBottomSheet and AlertDialog (Material extraLarge)." + }, + { + "name": "radius-full", + "value": "9999px", + "usage": "Buttons, TextButtons, IconButton ripples, portrait discs (CircleShape)." + } + ] + }, + "size": { + "note": "Fixed component sizes from the screens (dp = px).", + "tokens": [ + { + "name": "icon-pin", + "value": "16px", + "usage": "The pin mark beside a pinned poet." + }, + { + "name": "icon-inline", + "value": "18px", + "usage": "Share glyph inside a TextButton; download progress ring." + }, + { + "name": "icon-small", + "value": "20px", + "usage": "Couplet options chevron; small progress rings." + }, + { + "name": "icon", + "value": "24px", + "usage": "Default Material icon size in app bars and list rows." + }, + { + "name": "touch-compact", + "value": "32px", + "usage": "The couplet options IconButton." + }, + { + "name": "avatar-row", + "value": "44px", + "usage": "Poet portrait disc in list view." + }, + { + "name": "touch", + "value": "48px", + "usage": "IconButton and the non-interactive download tick box, so ticks and arrows line up." + }, + { + "name": "avatar-card", + "value": "84px", + "usage": "Poet portrait disc on a grid card." + }, + { + "name": "poet-grid-min", + "value": "132px", + "usage": "Minimum column width of the adaptive poet grid." + } + ] + } +} \ No newline at end of file diff --git a/design/wireframes/1-home.png b/design/wireframes/1-home.png new file mode 100644 index 0000000..f7916ab Binary files /dev/null and b/design/wireframes/1-home.png differ diff --git a/design/wireframes/2-poet-opened.png b/design/wireframes/2-poet-opened.png new file mode 100644 index 0000000..e8cc4ce Binary files /dev/null and b/design/wireframes/2-poet-opened.png differ diff --git a/design/wireframes/3-loading.png b/design/wireframes/3-loading.png new file mode 100644 index 0000000..8946e4a Binary files /dev/null and b/design/wireframes/3-loading.png differ diff --git a/design/wireframes/4-reading.png b/design/wireframes/4-reading.png new file mode 100644 index 0000000..aebcf6d Binary files /dev/null and b/design/wireframes/4-reading.png differ diff --git a/design/wireframes/5-reader-view.png b/design/wireframes/5-reader-view.png new file mode 100644 index 0000000..4ede9e1 Binary files /dev/null and b/design/wireframes/5-reader-view.png differ diff --git a/design/wireframes/6-dictionary.png b/design/wireframes/6-dictionary.png new file mode 100644 index 0000000..82642cc Binary files /dev/null and b/design/wireframes/6-dictionary.png differ diff --git a/design/wireframes/7-foldable.png b/design/wireframes/7-foldable.png new file mode 100644 index 0000000..7d789ae Binary files /dev/null and b/design/wireframes/7-foldable.png differ