194 lines
9.4 KiB
Markdown
194 lines
9.4 KiB
Markdown
# Design
|
|
|
|
```
|
|
Status: Current
|
|
Owner: _null
|
|
Last reviewed: 2026-08-18
|
|
Governs: docs/design/**, core/designsystem/** — the design tokens — and the
|
|
product's tone and interface copy
|
|
Review trigger: Any new user-facing screen or state; any change to the colour or
|
|
type tokens; any change to notification copy or to a privacy or
|
|
fertility disclaimer
|
|
```
|
|
|
|
## Where the detail is
|
|
|
|
The screen-by-screen specification and the actual words are
|
|
[`../planning/PRODUCT_PLAN.md`](../planning/PRODUCT_PLAN.md), and they are not
|
|
repeated here — a second copy of interface copy is how two versions of a
|
|
disclaimer come to exist.
|
|
|
|
| Subject | Section it owns |
|
|
| --- | --- |
|
|
| Onboarding, seven screens, with copy | §19, §56 |
|
|
| Navigation — four tabs | §20 |
|
|
| Today screen and its six dynamic states | §21, §22 |
|
|
| Period logging, period end, spotting | §23, §24, §25 |
|
|
| Calendar states and markers | §26 |
|
|
| Insights | §27 |
|
|
| Notification modes, types and flow | §28, §29, §30 |
|
|
| Incognito launcher | §32 |
|
|
| Look and feel, visual direction, typography, motion | §37, §38, §40, §41 |
|
|
| Colour | §39 — **superseded by [`BRAND_GUIDE.md`](BRAND_GUIDE.md)**; see [Colour](#colour) below |
|
|
| Artwork | §42 |
|
|
| Accessibility | §43 |
|
|
|
|
This document holds what governs those: the tone, and the rules that decide an
|
|
argument the specification did not anticipate.
|
|
|
|
## Tone
|
|
|
|
Calm, private, intelligent, adult. The app is a good utility with warmth — it is
|
|
not clinical, not childish, not gamified, and not stereotypically feminine as an
|
|
identity. It never celebrates a period and never alarms about one.
|
|
|
|
One paragraph, and it settles most small arguments: **the app speaks like
|
|
someone competent who is not making a fuss.** "Your period is late!" is out.
|
|
"Not yet?" with an updated forecast is in.
|
|
|
|
## Four rules that decide the arguments
|
|
|
|
**1. The number is the hero.** The forecast dominates the Today screen —
|
|
[§38](../planning/PRODUCT_PLAN.md). Anything competing with it for attention is
|
|
wrong, including anything of ours.
|
|
|
|
**2. Nothing asserts certainty the model does not have.** A window and a
|
|
confidence label, never a bare exact date presented as fact. "Estimated
|
|
ovulation", never "you are ovulating today". Every **screen** that shows a
|
|
fertility estimate carries the not-contraception line, which is where
|
|
[§18](../planning/PRODUCT_PLAN.md) scopes it — "near fertility features and in
|
|
About". Notification copy is the deliberate exception: it says "Estimated
|
|
fertile window" and stops, because a disclaimer is a long sentence about
|
|
fertility on a lock screen.
|
|
|
|
**3. State is never colour alone.** Confirmed period is a solid fill, predicted
|
|
is dotted or outlined, fertile window is a ring, ovulation is its own small
|
|
marker — distinguishable in greyscale, because that is also what makes them
|
|
distinguishable to a colourblind user and to a screenshot in a bug report.
|
|
Predicted and confirmed days must never look identical.
|
|
|
|
**4. Ads never touch a health action.** No banner in onboarding, in the
|
|
period-start confirmation, in the period-end confirmation, or between steps of a
|
|
health workflow — and never an interstitial after logging
|
|
([§33](../planning/PRODUCT_PLAN.md)).
|
|
|
|
**The banner space is reserved before there is an ad to put in it.** §48 wants no
|
|
layout jump when one loads and a graceful gap when one fails, and both are
|
|
properties of the space existing whether or not it is filled. Retrofitting the
|
|
reservation in Batch 07 means shipping the jump first and finding it in a QA
|
|
round. It is empty and invisible now — deliberately not a "your ad here" box,
|
|
which would be a placeholder for the thing a user pays to remove.
|
|
|
|
## Colour
|
|
|
|
The palette is [`BRAND_GUIDE.md`](BRAND_GUIDE.md), supplied by the project owner
|
|
on 2026-08-18. It **supersedes** the sketch in
|
|
[`../planning/PRODUCT_PLAN.md` §39](../planning/PRODUCT_PLAN.md) where the two
|
|
differ, and one difference is worth stating rather than leaving to be discovered:
|
|
|
|
> **§39 asks for a muted teal or sage fertile window. The brand guide is plum
|
|
> and rose throughout, and gives fertility its own pink (`#E891AE`).**
|
|
|
|
The guide wins — it is newer, it is the owner's, and a teal accent inside this
|
|
identity would look borrowed from another app. What §39 was protecting is kept
|
|
by other means, and this is the part that matters: **the calendar's states
|
|
differ in shape, not only in colour.** A solid disc, a dotted ring, a continuous
|
|
ring and a star stay tellable apart in greyscale and to a colourblind user, which
|
|
is what §43 actually requires. Colour is the second signal here, never the only
|
|
one.
|
|
|
|
The two rules from §39 that survive unchanged, because they are product
|
|
decisions rather than colour preferences:
|
|
|
|
- **Period state** is a sophisticated rose or plum — never graphic blood-red.
|
|
- **Nothing reads as "safe".** The fertile window is pink rather than green for
|
|
the same reason the labels say "Lower likelihood": this app never gives
|
|
permission.
|
|
|
|
Everything goes through Material 3 colour roles and centralized tokens in
|
|
`core/designsystem`. **No hard-coded colours in a Composable** — a colour
|
|
literal outside the token file is a review failure.
|
|
|
|
## Both themes, every time
|
|
|
|
Dark mode was broken for the whole of Batch 01 and nothing said so. `PeriodTheme`
|
|
was not wrapping its content in a `Surface`, so any `Text` without an explicit
|
|
colour inherited Material's default — black — and the app's own background never
|
|
painted at all. **In light mode that looked correct by accident**, because dark
|
|
text on cream is what was wanted anyway; in dark mode the onboarding headings
|
|
were near-black on charcoal.
|
|
|
|
No test caught it and no test easily would. It was found by opening the app.
|
|
|
|
Two rules follow. Every screen gets a **preview pair, light and dark** — cheap,
|
|
and it puts the failure in front of whoever is editing. And the `Surface` lives
|
|
in the theme rather than in each screen, so a screen without a `Scaffold` cannot
|
|
forget it.
|
|
|
|
## The states most often left undesigned
|
|
|
|
Designed here on purpose, because they are the two most people meet first:
|
|
|
|
- **Empty** — no periods logged yet. It has to make the next action obvious
|
|
rather than apologise.
|
|
- **Learning** — one or two cycles recorded. The app says it is still learning
|
|
rather than showing a confident forecast it has not earned. "Getting to know
|
|
your pattern", not a percentage.
|
|
|
|
## The in-app artwork is placeholder, and says so here
|
|
|
|
Every illustration and calendar marker in the app is a **Compose vector path**
|
|
in [`core/designsystem/src/main/kotlin/dev/privacyllc/period/designsystem/art/`](../../core/designsystem/src/main/kotlin/dev/privacyllc/period/designsystem/art/), written as PRODUCT_PLAN.md §42 asks: polished
|
|
placeholders behind replaceable names, so real artwork changes one function body
|
|
and no call site.
|
|
|
|
Real brand artwork exists — the owner's sources in [`brand/`](brand/) and the
|
|
three exported marks in [`../data/img/`](../data/img/) — and none of it is in the
|
|
app yet. The launcher icon keeps its simplified vector because the supplied
|
|
emblem has content close to its edges and an adaptive icon masks about a quarter
|
|
of the canvas away, so dropping it in unmodified would crop the shield. Fitting
|
|
it to the safe zone is Batch 08's final-artwork work.
|
|
|
|
### What the artwork has to do
|
|
|
|
Two things at once, stated by the owner and recorded here because it is the brief
|
|
every remaining art issue is judged against: **welcoming, and visibly private.**
|
|
Not alternating between them screen by screen — a warm opening followed by a bare
|
|
form is how onboarding reads today, and the privacy idea currently appears on
|
|
exactly one screen out of seven.
|
|
|
|
Welcoming is the harder half to get right without breaking [Tone](#tone): warmth
|
|
here means an app that looks like it was made with care, never one that
|
|
celebrates a period or decorates it. Private is already half-solved in the
|
|
vocabulary — `PrivacyIllustration` encloses a ring in a shield, and enclosure
|
|
reads as protection without reaching for a padlock, which would read as a
|
|
security product rather than a calm one.
|
|
|
|
The remaining art is tracked as issues #29 to #32, not listed here.
|
|
|
|
The visual language is deliberately narrow — **overlapping circular forms** and
|
|
nothing else. §42's forbidden list (blood drops, tampons, pads, uterus imagery,
|
|
gender symbols, anatomy) is a product decision rather than squeamishness: this
|
|
app gets opened in public, and somebody glancing over a shoulder should learn
|
|
nothing. That is a property of the pictures as much as of the notification text.
|
|
|
|
**Note the deliberate inconsistency with `docs/data/img/`,** where a placeholder
|
|
is forbidden. The rule there is that an image which looks finished outlives the
|
|
issue that would have replaced it, because nobody files a ticket against
|
|
something that appears done. In-app assets escape that because §42 asks for them
|
|
explicitly and the replaceable-name mechanism is what keeps them replaceable —
|
|
and because a screen with no illustration cannot be evaluated at all, while a
|
|
project card with no icon simply shows initials.
|
|
|
|
## Include the rejected version
|
|
|
|
For any decision that was genuinely close, record what was not chosen and why.
|
|
Without it the same option is proposed again every few months and re-argued from
|
|
nothing. The first entries belong to whoever makes those calls; the
|
|
specification's `Avoid:` lists are already a partial record of them.
|
|
|
|
## What does not belong here
|
|
|
|
- How it is built — [`../architecture/README.md`](../architecture/README.md)
|
|
- Scope and audience — [`../planning/PROJECT_PLAN.md`](../planning/PROJECT_PLAN.md)
|