Add the design system extracted from the app
design/ holds the tokens for all six themes, the type scale, spacing, radii and sizes, the brand and usage guidelines, a guide and static preview for each component, the logo and icons as SVG, and wireframes of the tablet and foldable column layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhmDdrN5hgWrgRcFLAMsV6
This commit is contained in:
49 files changed
+1401
No files matched your search
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
<!-- @dsCard group="Navigation" height=170 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj" dir="rtl" lang="fa">
|
||||
<div class="gj-searchbar"><div class="gj-search-poems"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zm-6,0C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/></svg>جستوجوی شعرها برای «حافظ»</div><div class="gj-field"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zm-6,0C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/></svg><span style="color:var(--on-surface)">حافظ</span></div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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`.
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- @dsCard group="Reading" height=120 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa">
|
||||
<nav class="gj-crumbs" aria-label="مسیر"><a>حافظ</a><span class="gj-sep"> » </span><a>غزلیات</a><span class="gj-sep"> » </span><span class="gj-here" aria-current="page">غزل شمارهٔ ۳</span></nav>
|
||||
<div class="gj-metre">مفاعیلن مفاعیلن مفاعیلن مفاعیلن (هزج مثمن سالم)</div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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).
|
||||
@@ -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`.
|
||||
@@ -0,0 +1,10 @@
|
||||
<!-- @dsCard group="Reading" height=330 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa">
|
||||
<div class="gj-couplet"><p class="gj-verse is-first">الا یا ایها الساقی ادر کأسا و ناولها</p><p class="gj-verse is-second">که عشق آسان نمود اول ولی افتاد مشکلها</p>
|
||||
<div class="gj-couplet-toggle"><button class="gj-icon-btn" aria-label="گزینههای این بیت" aria-expanded="true"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg></button></div>
|
||||
<div class="gj-actions"><button class="gj-text-btn">ذخیرهٔ این بخش</button><button class="gj-text-btn">رونوشت</button><button class="gj-text-btn"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z"/></svg>همرسانی</button></div></div>
|
||||
<div class="gj-couplet is-nastaliq"><p class="gj-verse is-first">به بوی نافهای کاخر صبا زان طره بگشاید</p><p class="gj-verse is-second">ز تاب جعد مشکینش چه خون افتاد در دلها</p>
|
||||
<div class="gj-couplet-toggle"><button class="gj-icon-btn" aria-label="گزینههای این بیت" aria-expanded="false"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg></button></div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!-- @dsCard height=288 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>
|
||||
html,body{margin:0;background:var(--surface)}
|
||||
.c{position:relative;width:960px;height:288px;overflow:hidden;background:var(--surface)}
|
||||
svg{position:absolute;inset:0}
|
||||
.p{fill:var(--primary)}.pc{fill:var(--primary-container)}.s{fill:var(--secondary)}.sc{fill:var(--secondary-container)}.ink{fill:var(--on-background)}.cut{fill:var(--surface)}
|
||||
.b{rx:var(--radius-md)}
|
||||
.t{position:absolute;left:var(--space-32);bottom:var(--space-24);max-width:440px}
|
||||
.n{font:400 110px/.95 var(--font-libron);color:var(--on-surface);margin:0;letter-spacing:-1px}
|
||||
.g{font:400 14px/20px var(--font-libron);color:var(--on-surface-variant);margin:var(--space-12) 0 0}
|
||||
</style></head><body><div class="c">
|
||||
<svg width="960" height="288" viewBox="0 0 960 288" aria-hidden="true">
|
||||
<!-- blocks: primary 240×288 slab (bleeds top and bottom), secondary 160×120, secondary-container 160×96, on-background 160×72, primary-container 80×120; corners radius-md
|
||||
arrangement: one tall turquoise slab with satellites stacked flush on its far side, like a tile panel
|
||||
pattern: literal motif, the khatam eight-point star from the launcher mark and the "Persian tilework" note in Theme.kt, cut in surface out of the slab, 12 stars at an 80px pitch (space-20×4)
|
||||
scales: pitch 80 = 4×space-20, gaps space-8, star 48 = 2×space-24 -->
|
||||
<defs><path id="k" d="M0,-24 L7.03,-16.97 L16.97,-16.97 L16.97,-7.03 L24,0 L16.97,7.03 L16.97,16.97 L7.03,16.97 L0,24 L-7.03,16.97 L-16.97,16.97 L-16.97,7.03 L-24,0 L-16.97,-7.03 L-16.97,-16.97 L-7.03,-16.97 Z"/></defs>
|
||||
<rect class="pc b" x="480" y="160" width="72" height="128"/>
|
||||
<rect class="p b" x="560" y="-12" width="240" height="312"/>
|
||||
<g class="cut">
|
||||
<use href="#k" x="600" y="40"/><use href="#k" x="680" y="40"/><use href="#k" x="760" y="40"/>
|
||||
<use href="#k" x="600" y="120"/><use href="#k" x="680" y="120"/><use href="#k" x="760" y="120"/>
|
||||
<use href="#k" x="600" y="200"/><use href="#k" x="680" y="200"/><use href="#k" x="760" y="200"/>
|
||||
<use href="#k" x="600" y="280"/><use href="#k" x="680" y="280"/><use href="#k" x="760" y="280"/>
|
||||
</g>
|
||||
<rect class="s b" x="808" y="-12" width="164" height="124"/>
|
||||
<rect class="sc b" x="808" y="120" width="164" height="88"/>
|
||||
<rect class="ink b" x="808" y="216" width="164" height="84"/>
|
||||
</svg>
|
||||
<div class="t"><p class="n">Ganjoor</p><p class="g">Persian poetry, set for long reading: tilework turquoise, saffron and paper.</p></div>
|
||||
</div></body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- @dsCard group="Status" height=110 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa" style="display:flex;gap:24px;align-items:center">
|
||||
<div style="text-align:center"><button class="gj-icon-btn" aria-label="دریافت"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5,20h14v-2H5v2zM19,9h-4V3H9v6H5l7,7 7,-7z"/></svg></button><div class="cap" style="text-align:center">Idle</div></div>
|
||||
<div style="text-align:center"><button class="gj-icon-btn" aria-label="لغو دریافت"><span class="gj-ring"></span></button><div class="cap" style="text-align:center">Downloading</div></div>
|
||||
<div style="text-align:center"><span class="gj-dl is-saved" role="img" aria-label="ذخیره شده"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg></span><div class="cap" style="text-align:center">Saved</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- @dsCard group="Selection" height=150 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad stack" dir="rtl" lang="fa">
|
||||
<div class="gj-chips" role="group"><button class="gj-chip" aria-pressed="true"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>برگزیده</button><button class="gj-chip" aria-pressed="false">ترتیب گنجور</button><button class="gj-chip" aria-pressed="false">الفبایی</button></div>
|
||||
<div class="gj-chips" role="group"><button class="gj-chip" aria-pressed="false">سیستم</button><button class="gj-chip" aria-pressed="false">روشن</button><button class="gj-chip" aria-pressed="false">تاریک</button><button class="gj-chip" aria-pressed="true"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>کاغذی</button><button class="gj-chip" aria-pressed="false">کاغذی شب</button></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- @dsCard group="Actions" height=150 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad stack" dir="rtl" lang="fa">
|
||||
<div><div class="cap">Not saved</div><div class="gj-actions"><button class="gj-text-btn is-neutral">ذخیرهٔ این بخش</button><button class="gj-text-btn">رونوشت</button><button class="gj-text-btn"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z"/></svg>همرسانی</button></div></div>
|
||||
<div><div class="cap">Saved</div><div class="gj-actions"><button class="gj-text-btn">ذخیره شد</button><button class="gj-text-btn">رونوشت</button><button class="gj-text-btn"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z"/></svg>همرسانی</button></div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- @dsCard group="Poets" height=300 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa"><div class="gj-poet-grid">
|
||||
<div class="gj-card" tabindex="0"><div class="gj-portrait is-card">ح</div><div class="gj-name"><svg class="gj-pin" viewBox="0 0 24 24" aria-label="برداشتن سنجاق"><path d="M16,9V4l1,0c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H7C6.45,2 6,2.45 6,3v0c0,0.55 0.45,1 1,1l1,0v5c0,1.66 -1.34,3 -3,3h0v2h5.97v7l1,1l1,-1v-7H19v-2h0C17.34,12 16,10.66 16,9z"/></svg>حافظ</div></div>
|
||||
<div class="gj-card" tabindex="0"><div class="gj-portrait is-card">خ</div><div class="gj-name">خیام</div></div>
|
||||
<div class="gj-card" tabindex="0"><div class="gj-portrait is-card">پ</div><div class="gj-name">پروین اعتصامی</div></div>
|
||||
</div></div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- @dsCard group="Poets" height=230 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj" dir="rtl" lang="fa">
|
||||
<div class="gj-row" tabindex="0"><div class="gj-portrait is-row">ح</div><div class="gj-row-text"><div class="gj-name" style="text-align:start">حافظ</div></div><div class="gj-row-trailing"><svg class="gj-pin" viewBox="0 0 24 24" aria-label="برداشتن سنجاق"><path d="M16,9V4l1,0c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H7C6.45,2 6,2.45 6,3v0c0,0.55 0.45,1 1,1l1,0v5c0,1.66 -1.34,3 -3,3h0v2h5.97v7l1,1l1,-1v-7H19v-2h0C17.34,12 16,10.66 16,9z"/></svg><span class="gj-dl is-saved" role="img" aria-label="ذخیره شده"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg></span></div></div>
|
||||
<div class="gj-row" tabindex="0"><div class="gj-portrait is-row">س</div><div class="gj-row-text"><div class="gj-name">سعدی</div></div><div class="gj-row-trailing"><button class="gj-icon-btn" aria-label="لغو دریافت"><span class="gj-ring"></span></button></div></div>
|
||||
<div class="gj-row" tabindex="0"><div class="gj-portrait is-row">م</div><div class="gj-row-text"><div class="gj-name">مولانا</div></div><div class="gj-row-trailing"><button class="gj-icon-btn" aria-label="دریافت"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5,20h14v-2H5v2zM19,9h-4V3H9v6H5l7,7 7,-7z"/></svg></button></div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- @dsCard group="Overlays" height=560 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj" dir="rtl" lang="fa" style="background:var(--surface);padding-top:24px">
|
||||
<div class="gj-sheet"><div class="gj-handle"></div>
|
||||
<div class="gj-label">پوسته</div><div class="gj-chips"><button class="gj-chip" aria-pressed="false">سیستم</button><button class="gj-chip" aria-pressed="true"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>روشن</button><button class="gj-chip" aria-pressed="false">تاریک</button><button class="gj-chip" aria-pressed="false">کاغذی</button><button class="gj-chip" aria-pressed="false">کاغذی شب</button></div>
|
||||
<div class="gj-toggle"><div class="gj-toggle-text"><div class="gj-toggle-title">پسزمینهٔ سیاه (OLED)</div><div class="gj-toggle-note">روی پوستههای تاریک اعمال میشود</div></div><button class="gj-switch" role="switch" aria-checked="false" aria-label="OLED"></button></div>
|
||||
<div class="gj-label">قلم</div><div class="gj-chips"><button class="gj-chip" aria-pressed="true"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>نسخ</button><button class="gj-chip" aria-pressed="false">نستعلیق</button></div>
|
||||
<div class="gj-label">اندازهٔ متن</div><input type="range" min="14" max="40" step="2" value="22" style="accent-color:var(--primary);width:100%">
|
||||
<p class="gj-verse" style="margin:0">الا یا ایها الساقی ادر کأسا و ناولها</p>
|
||||
</div></div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,4 @@
|
||||
<!-- @dsCard group="Selection" height=90 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa"><div class="gj-label">پوسته</div><div class="gj-label">اندازهٔ متن</div></div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- @dsCard group="Selection" height=170 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj pad" dir="rtl" lang="fa">
|
||||
<div class="gj-toggle"><div class="gj-toggle-text"><div class="gj-toggle-title">پسزمینهٔ سیاه (OLED)</div><div class="gj-toggle-note">روی پوستههای تاریک اعمال میشود و در نمایشگر OLED باتری کمتری میبرد</div></div><button class="gj-switch" role="switch" aria-checked="true" aria-label="پسزمینهٔ سیاه"></button></div>
|
||||
<div class="gj-toggle"><div class="gj-toggle-text"><div class="gj-toggle-title">حالت برونخط</div><div class="gj-toggle-note">فقط از شعرهای ذخیرهشده بخوان</div></div><button class="gj-switch" role="switch" aria-checked="false" aria-label="حالت برونخط"></button></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- @dsCard group="Navigation" height=200 -->
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:var(--surface)} .pad{padding:16px} .stack{display:flex;flex-direction:column;gap:16px} .cap{font:12px/16px var(--font-libron);color:var(--on-surface-variant);direction:ltr;text-align:left;margin-bottom:4px}</style></head><body>
|
||||
<div class="gj" dir="rtl" lang="fa">
|
||||
<div class="gj-topbar"><button class="gj-icon-btn gj-nav" aria-label="بازگشت"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg></button><div class="gj-topbar-title">غزل شمارهٔ ۳</div>
|
||||
<div class="gj-topbar-actions"><button class="gj-icon-btn" aria-label="خانه"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg></button><button class="gj-icon-btn" aria-label="همرسانی"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z"/></svg></button><button class="gj-icon-btn gj-on" aria-label="حذف نشانک"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg></button><button class="gj-icon-btn" aria-label="تنظیمات خواندن"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg></button></div></div>
|
||||
<hr class="gj-divider">
|
||||
<div class="gj-topbar"><div class="gj-topbar-title">گنجور</div><div class="gj-topbar-actions"><button class="gj-icon-btn" aria-label="نمایش فهرستی"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3,3v8h8V3H3zM9,9H5V5h4V9zM3,13v8h8v-8H3zM9,19H5v-4h4V19zM13,3v8h8V3H13zM19,9h-4V5h4V9zM13,13v8h8v-8H13zM19,19h-4v-4h4V19z"/></svg></button><button class="gj-icon-btn" aria-label="زبان"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"/></svg></button><button class="gj-icon-btn" aria-label="نشانکها"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg></button><button class="gj-icon-btn" aria-label="دریافتها"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5,20h14v-2H5v2zM19,9h-4V3H9v6H5l7,7 7,-7z"/></svg></button><button class="gj-icon-btn" aria-label="تنظیمات خواندن"><svg class="gj-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg></button></div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -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; }
|
||||
Reference in new issue
Block a user