ganjoorandroid/design
anas 41d776a35d Merge main, keeping only the part of this branch that is still needed
main has moved on since this branch opened, and most of what it carried has been
answered better there:

- the reading settings no longer need a fallback to the sheet, because they fold
  the columns away and take the room that frees (aab8b2e);
- the two panels no longer share a width, because the dictionary is now 216dp
  against the settings' 360dp.

Both were dropped: the conflicting files are taken from main as they stand.

What survives is the reason this branch exists. Which container the dictionary
uses is still decided by the width of the *window*, and that is the wrong
question — what matters is what is left of the page once the columns have taken
theirs. On a 700dp foldable that is about 450dp, and a panel beside it leaves
the verse a couple of characters a line. It now opens beside the poem only while
the page keeps 400dp, and falls back to the sheet below that, which covers the
foot of the poem but leaves every line whole.

Recalculated for the narrower panel: a tablet keeps the panel (~900dp page less
216dp leaves 684dp), a foldable in portrait does not (236dp).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-07 18:33:24 +02:00
..
assets Add the design system extracted from the app 2026-10-07 14:06:52 +00:00
components Merge main, keeping only the part of this branch that is still needed 2026-10-07 18:33:24 +02:00
wireframes Set each couplet in a dim rounded card, on every screen size 2026-10-07 16:04:08 +00:00
README.md Open reading settings in the left-hand panel on large screens 2026-10-07 14:51:12 +00:00
tokens.json Add the design system extracted from the app 2026-10-07 14:06:52 +00:00

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. The dictionary and reading settings open in a panel on the left, beside the poem rather than over it. Phones are unchanged.