149 lines
7.0 KiB
Markdown
149 lines
7.0 KiB
Markdown
# Design
|
||
|
||
```
|
||
Status: Current
|
||
Owner: _null
|
||
Last reviewed: 2026-08-18
|
||
Governs: docs/design/**, the design tokens in core/designsystem, 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, colour, typography, motion | §37–§41 |
|
||
| 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". Fertility copy carries the
|
||
not-contraception line wherever it appears.
|
||
|
||
**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.
|
||
|
||
**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.
|
||
|
||
**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)). Banner space is reserved in the layout so a
|
||
failed ad does not move the content.
|
||
|
||
## Colour, in one line each
|
||
|
||
The palette is [§39](../planning/PRODUCT_PLAN.md); the constraints on it are:
|
||
|
||
- **Not pink as the whole identity.** Deep plum, muted berry, soft lavender,
|
||
warm cream, charcoal, muted sage/teal.
|
||
- **Period state** is a sophisticated berry or plum — never graphic blood-red.
|
||
- **Fertile window** is muted teal or sage — never bright green, which reads as
|
||
*safe* and this app must never say that.
|
||
- Everything goes through Material 3 colour roles and centralized tokens in
|
||
`core/designsystem`. **No hard-coded colours in a Composable** — the guard is
|
||
that 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 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.
|
||
|
||
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)
|