style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -16,13 +16,13 @@ created: 2026-06-10
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Tool | none (custom CSS token layer) |
|
||||
| Preset | not applicable |
|
||||
| Component library | none — hand-rolled components using CSS custom properties |
|
||||
| Icon library | lucide-react@1.17.0 |
|
||||
| Font | system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif |
|
||||
| Property | Value |
|
||||
| ----------------- | -------------------------------------------------------------------- |
|
||||
| Tool | none (custom CSS token layer) |
|
||||
| Preset | not applicable |
|
||||
| Component library | none — hand-rolled components using CSS custom properties |
|
||||
| Icon library | lucide-react@1.17.0 |
|
||||
| Font | system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif |
|
||||
|
||||
Source: `apps/pwa/src/styles/tokens.css` — fully established in Phase 2 (D-01/D-02).
|
||||
No new design-system tooling introduced in this phase.
|
||||
@@ -33,17 +33,18 @@ No new design-system tooling introduced in this phase.
|
||||
|
||||
Declared values from `apps/pwa/src/styles/tokens.css` — no changes in this phase:
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| --space-1 | 4px | Icon gaps, inline padding, dot-label gaps |
|
||||
| --space-2 | 8px | Compact element spacing, badge padding |
|
||||
| --space-3 | 12px | Form field internal padding |
|
||||
| --space-4 | 16px | Default element spacing, card padding |
|
||||
| --space-6 | 24px | Section padding, sheet internal padding |
|
||||
| --space-8 | 32px | Layout gaps, modal vertical padding |
|
||||
| --space-12 | 48px | Major section breaks |
|
||||
| Token | Value | Usage |
|
||||
| ---------- | ----- | ----------------------------------------- |
|
||||
| --space-1 | 4px | Icon gaps, inline padding, dot-label gaps |
|
||||
| --space-2 | 8px | Compact element spacing, badge padding |
|
||||
| --space-3 | 12px | Form field internal padding |
|
||||
| --space-4 | 16px | Default element spacing, card padding |
|
||||
| --space-6 | 24px | Section padding, sheet internal padding |
|
||||
| --space-8 | 32px | Layout gaps, modal vertical padding |
|
||||
| --space-12 | 48px | Major section breaks |
|
||||
|
||||
Exceptions:
|
||||
|
||||
- Touch targets: minimum 44px height on all interactive elements (EventForm inputs, recurrence bound control, series-edit prompt buttons). 48px on primary CTAs (per existing PushPermissionPrompt pattern).
|
||||
- All-day banner row: height is not constrained to spacing scale; it follows the Schedule-X all-day row height. Do not override it.
|
||||
|
||||
@@ -53,12 +54,12 @@ Exceptions:
|
||||
|
||||
From `apps/pwa/src/styles/tokens.css` — no new sizes or weights introduced in this phase:
|
||||
|
||||
| Role | Size | Weight | Line Height | Used in Phase 6 |
|
||||
|------|------|--------|-------------|-----------------|
|
||||
| Body | 15px | 400 | 1.5 | Auth splash body text, session-expired interstitial copy, form labels |
|
||||
| Label | 13px | 400 | 1.4 | Recurrence bound control labels, "repeat until" date caption, series-edit prompt sub-copy |
|
||||
| Heading | 18px | 600 | 1.25 | Auth splash heading ("Signing you in"), series-edit prompt heading |
|
||||
| Display | 24px | 600 | 1.2 | Not used in Phase 6 new surfaces |
|
||||
| Role | Size | Weight | Line Height | Used in Phase 6 |
|
||||
| ------- | ---- | ------ | ----------- | ----------------------------------------------------------------------------------------- |
|
||||
| Body | 15px | 400 | 1.5 | Auth splash body text, session-expired interstitial copy, form labels |
|
||||
| Label | 13px | 400 | 1.4 | Recurrence bound control labels, "repeat until" date caption, series-edit prompt sub-copy |
|
||||
| Heading | 18px | 600 | 1.25 | Auth splash heading ("Signing you in"), series-edit prompt heading |
|
||||
| Display | 24px | 600 | 1.2 | Not used in Phase 6 new surfaces |
|
||||
|
||||
---
|
||||
|
||||
@@ -66,31 +67,35 @@ From `apps/pwa/src/styles/tokens.css` — no new sizes or weights introduced in
|
||||
|
||||
From `apps/pwa/src/styles/tokens.css` — no new colors introduced in this phase:
|
||||
|
||||
| Role | Value | Usage |
|
||||
|------|-------|-------|
|
||||
| Dominant (60%) | --color-surface: #FFFFFF | Auth splash background, modal/sheet backgrounds, EventForm background |
|
||||
| Role | Value | Usage |
|
||||
| --------------- | ---------------------------- | ---------------------------------------------------------------------------- |
|
||||
| Dominant (60%) | --color-surface: #FFFFFF | Auth splash background, modal/sheet backgrounds, EventForm background |
|
||||
| Secondary (30%) | --color-surface-dim: #F7F7F8 | Series-edit prompt background band, all-day visual treatment background fill |
|
||||
| Accent (10%) | --color-member-0: #4A90D9 | Primary CTA buttons only: "Turn On Notifications", "Save" in event form |
|
||||
| Destructive | --color-destructive: #DC2626 | Delete event, destructive actions only |
|
||||
| Accent (10%) | --color-member-0: #4A90D9 | Primary CTA buttons only: "Turn On Notifications", "Save" in event form |
|
||||
| Destructive | --color-destructive: #DC2626 | Delete event, destructive actions only |
|
||||
|
||||
Accent reserved for:
|
||||
|
||||
1. Primary CTA button fill in EventForm ("Save" / "Update Series")
|
||||
2. Focus ring (--color-focus-ring: #4A90D9) on interactive inputs
|
||||
|
||||
New usage decisions for Phase 6 surfaces:
|
||||
|
||||
**All-day event visual treatment (999.6):**
|
||||
|
||||
- All-day chips/banners in Schedule-X all-day row: use full-width pill style with the member's `--color-member-N` or `--color-shared-family` as background fill at 100% opacity.
|
||||
- Timed event chips: keep existing member color fill.
|
||||
- The visual distinction is achieved through shape and presentation (full-width pill vs. standard event block), not a new color. The existing `_familySync.color` per event drives the fill in both cases.
|
||||
|
||||
**Auth splash (999.2) + session-expired interstitial (999.3):**
|
||||
|
||||
- Full-screen overlay: `--color-surface` (#FFFFFF) background.
|
||||
- Spinner: `--color-member-0` (#4A90D9) — reuses the existing Loader2 + spin keyframe pattern.
|
||||
- Heading: `--color-text-primary` (#111318).
|
||||
- Body: `--color-text-secondary` (#6B7280).
|
||||
|
||||
**Series-edit prompt (999.9):**
|
||||
|
||||
- Confirmation sheet / dialog uses same surface + border system as existing DeleteConfirmationDialog.
|
||||
- No new accent color. Primary confirm action uses `--color-member-0` fill (same as Save CTA).
|
||||
|
||||
@@ -100,68 +105,72 @@ New usage decisions for Phase 6 surfaces:
|
||||
|
||||
### Auth splash — unauthenticated cold load (999.2)
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Heading | Signing you in |
|
||||
| Body | Taking you to the sign-in page… |
|
||||
| Element | Copy |
|
||||
| -------------------------------- | ---------------------------------------- |
|
||||
| Heading | Signing you in |
|
||||
| Body | Taking you to the sign-in page… |
|
||||
| Fallback (one-shot guard failed) | Sign-in required. Tap here to try again. |
|
||||
|
||||
Rules:
|
||||
|
||||
- No punctuation on the heading.
|
||||
- Body uses an ellipsis (…, U+2026) not three dots.
|
||||
- The "Sign-in required" fallback is the dead-end only — not the primary unauthenticated path.
|
||||
|
||||
### Session-expired interstitial (999.3)
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Heading | Session expired |
|
||||
| Body | Signing you back in… |
|
||||
| Element | Copy |
|
||||
| ------- | -------------------- |
|
||||
| Heading | Session expired |
|
||||
| Body | Signing you back in… |
|
||||
|
||||
Rules:
|
||||
|
||||
- This interstitial replaces the hanging/generic error state. It is shown for ≤2s before `window.location.href = '/api/login'` fires.
|
||||
- Do not show a dismiss button — the redirect is automatic.
|
||||
|
||||
### EventForm — recurrence bound control (999.8)
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Bound type label | Ends |
|
||||
| Option: no bound | Never |
|
||||
| Option: repeat until | On date |
|
||||
| Option: count | After N times |
|
||||
| Date input label | End date |
|
||||
| Count input label | Occurrences |
|
||||
| Count input placeholder | e.g. 10 |
|
||||
| Validation error: count < 1 | Must be at least 1 occurrence |
|
||||
| Element | Copy |
|
||||
| ------------------------------- | --------------------------------------- |
|
||||
| Bound type label | Ends |
|
||||
| Option: no bound | Never |
|
||||
| Option: repeat until | On date |
|
||||
| Option: count | After N times |
|
||||
| Date input label | End date |
|
||||
| Count input label | Occurrences |
|
||||
| Count input placeholder | e.g. 10 |
|
||||
| Validation error: count < 1 | Must be at least 1 occurrence |
|
||||
| Validation error: until < start | End date must be after the event starts |
|
||||
|
||||
### EventForm — series-edit confirmation (999.9)
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Sheet/dialog heading | Edit recurring series |
|
||||
| Body | This will update all occurrences of this event. |
|
||||
| Confirm CTA | Update series |
|
||||
| Cancel | Cancel |
|
||||
| Element | Copy |
|
||||
| -------------------- | ----------------------------------------------- |
|
||||
| Sheet/dialog heading | Edit recurring series |
|
||||
| Body | This will update all occurrences of this event. |
|
||||
| Confirm CTA | Update series |
|
||||
| Cancel | Cancel |
|
||||
|
||||
Rules:
|
||||
|
||||
- "Update series" is the primary action (accent-filled button).
|
||||
- "Cancel" is a ghost/secondary button.
|
||||
- No destructive color on confirm — this is an edit, not a delete.
|
||||
|
||||
### EventForm — primary CTAs
|
||||
|
||||
| Mode | CTA label |
|
||||
|------|-----------|
|
||||
| Create (non-recurring) | Save event |
|
||||
| Create (recurring) | Save event |
|
||||
| Edit (non-recurring) | Save changes |
|
||||
| Mode | CTA label |
|
||||
| --------------------------- | ------------- |
|
||||
| Create (non-recurring) | Save event |
|
||||
| Create (recurring) | Save event |
|
||||
| Edit (non-recurring) | Save changes |
|
||||
| Edit (recurring occurrence) | Update series |
|
||||
|
||||
### Sync indicators (D-13)
|
||||
|
||||
No copy change. Existing copy is canonical:
|
||||
|
||||
- Spinning: "Syncing…"
|
||||
- Done: "Saved"
|
||||
- Failed: "Didn't save. Try again." (or conflict variant)
|
||||
@@ -175,10 +184,10 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
|
||||
### Destructive actions
|
||||
|
||||
| Action | Trigger | Confirmation |
|
||||
|--------|---------|--------------|
|
||||
| Delete event | Trash2 icon in EventDetailPopover footer | Existing DeleteConfirmationDialog — "Delete event?" / "This can't be undone." / "Delete" (destructive-red) / "Cancel" |
|
||||
| Delete recurring series | (Not in scope for Phase 6 — whole-series edit only, not delete) | N/A |
|
||||
| Action | Trigger | Confirmation |
|
||||
| ----------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| Delete event | Trash2 icon in EventDetailPopover footer | Existing DeleteConfirmationDialog — "Delete event?" / "This can't be undone." / "Delete" (destructive-red) / "Cancel" |
|
||||
| Delete recurring series | (Not in scope for Phase 6 — whole-series edit only, not delete) | N/A |
|
||||
|
||||
---
|
||||
|
||||
@@ -189,11 +198,13 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
**Trigger:** `meQuery.isLoading` on initial mount (before auth state is known).
|
||||
|
||||
**Layout:**
|
||||
|
||||
- Full-screen centered column: `display:flex; flex-direction:column; align-items:center; justify-content:center; height:100dvh; background:var(--color-surface)`.
|
||||
- Stack: Loader2 spinner (24px, `--color-member-0`, spinning via global `@keyframes spin`) → 16px gap → Heading (18px/600) → 8px gap → Body (15px/400, `--color-text-secondary`).
|
||||
- No app chrome (no BottomTabBar, no AppNav).
|
||||
|
||||
**States:**
|
||||
|
||||
1. `meQuery.isLoading` → show spinner + "Signing you in" heading + "Taking you to the sign-in page…" body.
|
||||
2. `meQuery.isError` (opaqueredirect / 401), first attempt → trigger `maybeRedirectToLogin()` immediately; while the redirect is in-flight keep the spinner visible (same view).
|
||||
3. `meQuery.isError`, one-shot guard already fired (`familysync.loginRedirectAttempted` set) → replace body with "Sign-in required. Tap here to try again." with a tap handler that clears the flag and re-triggers login. No spinner in this dead-end state.
|
||||
@@ -205,6 +216,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
**Trigger:** Any query or mutation returns 401 / opaqueredirect after initial auth succeeds.
|
||||
|
||||
**Layout:** Same full-screen centered column as Surface 1, but:
|
||||
|
||||
- Heading: "Session expired"
|
||||
- Body: "Signing you back in…"
|
||||
- Spinner visible.
|
||||
@@ -217,6 +229,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
**Schedule-X context:** All-day events appear in the all-day row in week/day views and as full-width banners in month/agenda views. Schedule-X renders them with its own chip CSS.
|
||||
|
||||
**Treatment:**
|
||||
|
||||
- Override Schedule-X all-day chip styles to render as a full-width rounded pill (border-radius: 4px) spanning the full column width with the event's member color as solid background fill and white (`#FFFFFF`) label text.
|
||||
- Timed events keep their existing chip appearance (colored left border + lighter background tint, as Schedule-X default renders them with `--sx-color-primary`).
|
||||
- The visual distinction contract: **all-day = solid filled pill; timed = partial-fill chip with colored border accent**.
|
||||
@@ -228,6 +241,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
### Surface 4: EventForm — end-tracking + all-day off-by-one fix (999.7)
|
||||
|
||||
**Behavior contract:**
|
||||
|
||||
- On any `startDate` or `startTime` change: recalculate `endDate`/`endTime` to preserve the current duration.
|
||||
- Timed: `newEnd = newStart + (oldEnd - oldStart)`. If `oldEnd <= oldStart` (stale state), snap to `newStart + 1h`.
|
||||
- All-day: `newEndInclusive = newStartDate + (oldEndInclusive - oldStartDate)` in days. If span = 0, keep 0 (same-day). If `oldEnd < oldStart` (stale), snap `newEnd = newStart`.
|
||||
@@ -235,6 +249,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
- The all-day-edit off-by-one (D-05) is already fixed at `EventForm.tsx:199`. Verify the `exclusiveEndToInclusiveDate` helper still applies correctly in edit pre-fill; do not re-implement.
|
||||
|
||||
**Interaction:**
|
||||
|
||||
- No toast or indicator when end auto-advances — silent and expected.
|
||||
- The end date/time fields remain editable after the auto-advance; the user can override further.
|
||||
|
||||
@@ -243,6 +258,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
**Placement:** Appears below the frequency `<select>` in the recurrence section, shown only when recurrence ≠ "None".
|
||||
|
||||
**Layout:**
|
||||
|
||||
- Label: "Ends" (13px/400, `--color-text-secondary`).
|
||||
- Three-option `<select>` or segmented control:
|
||||
- "Never" (default)
|
||||
@@ -258,10 +274,12 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
**Trigger:** User taps "Save" on an EventForm that is editing a recurring occurrence (occurrence has a `uid` whose event has `hasRrule=true`).
|
||||
|
||||
**Pattern:** Use the existing bottom-sheet/dialog pattern matching `DeleteConfirmationDialog`:
|
||||
|
||||
- Phone (≤767px): bottom sheet sliding up from below.
|
||||
- Tablet/desktop (≥768px): centered dialog, max-width 480px.
|
||||
|
||||
**Layout:**
|
||||
|
||||
```
|
||||
[ Sheet/Dialog ]
|
||||
Heading: "Edit recurring series" (18px/600)
|
||||
@@ -273,6 +291,7 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
```
|
||||
|
||||
**Accessibility:**
|
||||
|
||||
- `role="dialog"`, `aria-modal="true"`, `aria-labelledby` pointing to the heading.
|
||||
- Focus trap — Tab/Shift+Tab cycle between Cancel and Update series.
|
||||
- Escape key fires Cancel.
|
||||
@@ -287,17 +306,23 @@ No new empty states introduced in this phase. Existing EmptyState and ListsEmpty
|
||||
|
||||
## Animation Contract
|
||||
|
||||
| Animation | Definition | Applied to |
|
||||
|-----------|-----------|-----------|
|
||||
| shimmer | `@keyframes shimmer` in tokens.css | SkeletonCalendar loading bars |
|
||||
| spin | `@keyframes spin` in tokens.css (global — do not redefine per-component) | Loader2 in SyncStateToast, PushPermissionPrompt, auth splash, session-expired interstitial |
|
||||
| pulse | `@keyframes pulse` — confirm presence in tokens.css or add it | LiveSyncIndicator reconnecting dot |
|
||||
| Animation | Definition | Applied to |
|
||||
| --------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| shimmer | `@keyframes shimmer` in tokens.css | SkeletonCalendar loading bars |
|
||||
| spin | `@keyframes spin` in tokens.css (global — do not redefine per-component) | Loader2 in SyncStateToast, PushPermissionPrompt, auth splash, session-expired interstitial |
|
||||
| pulse | `@keyframes pulse` — confirm presence in tokens.css or add it | LiveSyncIndicator reconnecting dot |
|
||||
|
||||
Note: If `@keyframes pulse` is not in tokens.css, add it alongside `@keyframes spin`:
|
||||
|
||||
```css
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -307,13 +332,13 @@ Note: If `@keyframes pulse` is not in tokens.css, add it alongside `@keyframes s
|
||||
|
||||
All new surfaces must meet:
|
||||
|
||||
| Requirement | Value |
|
||||
|-------------|-------|
|
||||
| Minimum touch target | 44px height (48px on primary CTAs) |
|
||||
| Focus management | Focus trap in all dialogs/sheets; Escape closes |
|
||||
| Live regions | `role="status"` for informational (spinner, sync); `role="alert"` for errors |
|
||||
| Color contrast | 4.5:1 for body text; 3:1 for large text (18px+/bold) |
|
||||
| Motion | `@keyframes spin` and `pulse` are UI-state feedback — not decorative; acceptable without `prefers-reduced-motion` guard, but a reduced-motion variant (opacity swap instead of spin) is a welcome addition if it fits the plan |
|
||||
| Requirement | Value |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Minimum touch target | 44px height (48px on primary CTAs) |
|
||||
| Focus management | Focus trap in all dialogs/sheets; Escape closes |
|
||||
| Live regions | `role="status"` for informational (spinner, sync); `role="alert"` for errors |
|
||||
| Color contrast | 4.5:1 for body text; 3:1 for large text (18px+/bold) |
|
||||
| Motion | `@keyframes spin` and `pulse` are UI-state feedback — not decorative; acceptable without `prefers-reduced-motion` guard, but a reduced-motion variant (opacity swap instead of spin) is a welcome addition if it fits the plan |
|
||||
|
||||
---
|
||||
|
||||
@@ -327,6 +352,7 @@ In-app UI icons remain lucide-react — this section covers only the brand mark,
|
||||
**Concept (D-BRAND-01):** Two overlapping rounded shapes — circles or rounded rectangles — suggesting two members sharing a space (the family) or two overlapping calendar tiles. The overlap region is the accent color at full opacity; each outer shape is the accent color at reduced opacity (≈60%). The wordmark "FamilySync" sits to the right of the glyph in the lockup variant; the glyph alone is the source for all raster icons.
|
||||
|
||||
**Geometry / construction:**
|
||||
|
||||
- Two circles, each 40px diameter on a 64px × 64px artboard (SVG viewBox="0 0 64 64").
|
||||
- Circle 1 center: (24, 32). Circle 2 center: (40, 32).
|
||||
- Overlap: the intersection region formed by both paths.
|
||||
@@ -335,15 +361,16 @@ In-app UI icons remain lucide-react — this section covers only the brand mark,
|
||||
|
||||
**Color usage:**
|
||||
|
||||
| Surface | Glyph fill | Wordmark fill |
|
||||
|---------|-----------|---------------|
|
||||
| Light background (#FFFFFF) | #4A90D9 (accent) | #111318 (--color-text-primary) |
|
||||
| Dark background (≥50% dark) | #FFFFFF | #FFFFFF |
|
||||
| Surface | Glyph fill | Wordmark fill |
|
||||
| --------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| Light background (#FFFFFF) | #4A90D9 (accent) | #111318 (--color-text-primary) |
|
||||
| Dark background (≥50% dark) | #FFFFFF | #FFFFFF |
|
||||
| Monochrome (print / favicon .ico) | currentColor (#111318 on light, #FFFFFF on dark) — single flat shape, no opacity split |
|
||||
|
||||
**Clear space:** Minimum clear space = 1× the glyph diameter (64px on the 64px artboard, i.e. one full glyph-width on all four sides at actual render size).
|
||||
|
||||
**Minimum size:**
|
||||
|
||||
- Glyph-only: 24px × 24px rendered (below this, detail is lost; use the monochrome flat variant).
|
||||
- Glyph + wordmark lockup: 120px wide minimum.
|
||||
|
||||
@@ -351,11 +378,11 @@ In-app UI icons remain lucide-react — this section covers only the brand mark,
|
||||
|
||||
### SVG Source-of-Truth Files
|
||||
|
||||
| File | Contents | Used as source for |
|
||||
|------|----------|--------------------|
|
||||
| `apps/pwa/src/assets/logo-glyph.svg` | Glyph only, viewBox="0 0 64 64", color-variable fills (`currentColor` + CSS custom property override) | All raster icon exports; in-app glyph-only placements |
|
||||
| `apps/pwa/src/assets/logo-lockup.svg` | Glyph + "FamilySync" wordmark, viewBox="0 0 240 64" | Auth splash, header lockup (if present) |
|
||||
| `apps/pwa/src/assets/logo-monochrome.svg` | Flat union-path glyph, single fill, viewBox="0 0 64 64" | favicon.ico source layer |
|
||||
| File | Contents | Used as source for |
|
||||
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
||||
| `apps/pwa/src/assets/logo-glyph.svg` | Glyph only, viewBox="0 0 64 64", color-variable fills (`currentColor` + CSS custom property override) | All raster icon exports; in-app glyph-only placements |
|
||||
| `apps/pwa/src/assets/logo-lockup.svg` | Glyph + "FamilySync" wordmark, viewBox="0 0 240 64" | Auth splash, header lockup (if present) |
|
||||
| `apps/pwa/src/assets/logo-monochrome.svg` | Flat union-path glyph, single fill, viewBox="0 0 64 64" | favicon.ico source layer |
|
||||
|
||||
All SVG files: no embedded raster data, no `<image>` elements, path-only. Minified with no comments before commit.
|
||||
|
||||
@@ -367,23 +394,24 @@ Export tool: any SVG-to-PNG renderer that preserves alpha (e.g. `sharp`, `Inksca
|
||||
|
||||
**Output list:**
|
||||
|
||||
| Output file | Size (px) | Format | Notes |
|
||||
|-------------|-----------|--------|-------|
|
||||
| `apps/pwa/public/icon-192.png` | 192×192 | PNG, RGBA | PWA manifest `any` icon |
|
||||
| `apps/pwa/public/icon-512.png` | 512×512 | PNG, RGBA | PWA manifest `any` icon |
|
||||
| `apps/pwa/public/icon-512-maskable.png` | 512×512 | PNG, RGBA | PWA manifest `maskable` icon — glyph centered in safe zone (see below) |
|
||||
| `apps/pwa/public/apple-touch-icon.png` | 180×180 | PNG, RGB (no alpha), white background | iOS home screen; Apple ignores alpha |
|
||||
| `apps/pwa/public/favicon-16.png` | 16×16 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon-32.png` | 32×32 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon-48.png` | 48×48 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon.svg` | — | SVG (copy of logo-glyph.svg) | Modern browsers; referenced as `<link rel="icon" type="image/svg+xml">` |
|
||||
| `apps/pwa/public/favicon.ico` | 16+32+48 multi-res | ICO | Legacy browsers; bundle the three PNG layers into a single .ico using `png-to-ico` or equivalent |
|
||||
| Output file | Size (px) | Format | Notes |
|
||||
| --------------------------------------- | ------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `apps/pwa/public/icon-192.png` | 192×192 | PNG, RGBA | PWA manifest `any` icon |
|
||||
| `apps/pwa/public/icon-512.png` | 512×512 | PNG, RGBA | PWA manifest `any` icon |
|
||||
| `apps/pwa/public/icon-512-maskable.png` | 512×512 | PNG, RGBA | PWA manifest `maskable` icon — glyph centered in safe zone (see below) |
|
||||
| `apps/pwa/public/apple-touch-icon.png` | 180×180 | PNG, RGB (no alpha), white background | iOS home screen; Apple ignores alpha |
|
||||
| `apps/pwa/public/favicon-16.png` | 16×16 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon-32.png` | 32×32 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon-48.png` | 48×48 | PNG | favicon.ico source layer |
|
||||
| `apps/pwa/public/favicon.svg` | — | SVG (copy of logo-glyph.svg) | Modern browsers; referenced as `<link rel="icon" type="image/svg+xml">` |
|
||||
| `apps/pwa/public/favicon.ico` | 16+32+48 multi-res | ICO | Legacy browsers; bundle the three PNG layers into a single .ico using `png-to-ico` or equivalent |
|
||||
|
||||
Replace the three existing placeholder files (`icon-192.png`, `icon-512.png`, `apple-touch-icon.png`) with the real exports. The script must be idempotent (re-running overwrites all outputs).
|
||||
|
||||
### Maskable Safe Zone
|
||||
|
||||
The maskable icon specification requires the primary visual to fit within the center 80% of the canvas (the "safe zone"). For a 512×512 canvas, the safe zone is the inner 409×409 px centered region. The glyph export for `icon-512-maskable.png` must:
|
||||
|
||||
- Scale the glyph to fit within 409×409 (≈80% of 512).
|
||||
- Center it on the 512×512 canvas.
|
||||
- Fill the outer 10% bleed area with the PWA `background_color` (#FFFFFF) so masked shapes (squircle, circle) show a clean white surround rather than transparency artifacts.
|
||||
@@ -421,12 +449,12 @@ Browser resolution order: SVG favicon first (Chrome 80+, Firefox 41+, Safari 12+
|
||||
|
||||
### In-App Logo Usage
|
||||
|
||||
| Surface | Variant | Size | Placement |
|
||||
|---------|---------|------|-----------|
|
||||
| Auth splash (Surface 1) | Glyph + wordmark lockup (`logo-lockup.svg`) | 120px wide (auto height ~32px) | Centered above the spinner stack; 24px gap below lockup, then spinner |
|
||||
| Session-expired interstitial (Surface 2) | Glyph only (`logo-glyph.svg`) | 32×32px | Centered above spinner; same vertical stack as auth splash |
|
||||
| App header / nav bar (if present) | Glyph only (`logo-glyph.svg`) | 24×24px | Leading slot of the top nav bar, 16px from left edge, vertically centered |
|
||||
| PWA install prompt / about screen | Glyph + wordmark lockup | 160px wide | Centered |
|
||||
| Surface | Variant | Size | Placement |
|
||||
| ---------------------------------------- | ------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------- |
|
||||
| Auth splash (Surface 1) | Glyph + wordmark lockup (`logo-lockup.svg`) | 120px wide (auto height ~32px) | Centered above the spinner stack; 24px gap below lockup, then spinner |
|
||||
| Session-expired interstitial (Surface 2) | Glyph only (`logo-glyph.svg`) | 32×32px | Centered above spinner; same vertical stack as auth splash |
|
||||
| App header / nav bar (if present) | Glyph only (`logo-glyph.svg`) | 24×24px | Leading slot of the top nav bar, 16px from left edge, vertically centered |
|
||||
| PWA install prompt / about screen | Glyph + wordmark lockup | 160px wide | Centered |
|
||||
|
||||
Spacing tokens used: 24px gap (`--space-6`) between lockup and spinner on auth splash; 16px left inset (`--space-4`) for header placement. These are drawn from the established 8-point scale.
|
||||
|
||||
@@ -434,27 +462,27 @@ Color: render SVGs using CSS `color` inheritance where possible so light/dark mo
|
||||
|
||||
### Asset Manifest
|
||||
|
||||
| File path | Format | Size(s) | Purpose | Referenced in |
|
||||
|-----------|--------|---------|---------|---------------|
|
||||
| `apps/pwa/src/assets/logo-glyph.svg` | SVG | 64×64 viewBox | SVG source of truth — glyph only | Export script, in-app `<img>` or inline SVG |
|
||||
| `apps/pwa/src/assets/logo-lockup.svg` | SVG | 240×64 viewBox | SVG source of truth — glyph + wordmark | Auth splash, install prompt |
|
||||
| `apps/pwa/src/assets/logo-monochrome.svg` | SVG | 64×64 viewBox | Monochrome union-path variant | favicon.ico source |
|
||||
| `apps/pwa/public/favicon.svg` | SVG | — | Modern browser favicon | `<link rel="icon" type="image/svg+xml">` in index.html |
|
||||
| `apps/pwa/public/favicon.ico` | ICO | 16+32+48 multi-res | Legacy browser favicon | `<link rel="icon" type="image/x-icon">` in index.html |
|
||||
| `apps/pwa/public/icon-192.png` | PNG (RGBA) | 192×192 | PWA manifest any icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/icon-512.png` | PNG (RGBA) | 512×512 | PWA manifest any icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/icon-512-maskable.png` | PNG (RGBA) | 512×512 | PWA manifest maskable icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/apple-touch-icon.png` | PNG (RGB, white bg) | 180×180 | iOS home screen icon | `<link rel="apple-touch-icon">` in index.html |
|
||||
| `apps/pwa/scripts/export-icons.ts` | TypeScript | — | Build-time raster export pipeline | `make icons` or equivalent |
|
||||
| File path | Format | Size(s) | Purpose | Referenced in |
|
||||
| ----------------------------------------- | ------------------- | ------------------ | -------------------------------------- | ------------------------------------------------------ |
|
||||
| `apps/pwa/src/assets/logo-glyph.svg` | SVG | 64×64 viewBox | SVG source of truth — glyph only | Export script, in-app `<img>` or inline SVG |
|
||||
| `apps/pwa/src/assets/logo-lockup.svg` | SVG | 240×64 viewBox | SVG source of truth — glyph + wordmark | Auth splash, install prompt |
|
||||
| `apps/pwa/src/assets/logo-monochrome.svg` | SVG | 64×64 viewBox | Monochrome union-path variant | favicon.ico source |
|
||||
| `apps/pwa/public/favicon.svg` | SVG | — | Modern browser favicon | `<link rel="icon" type="image/svg+xml">` in index.html |
|
||||
| `apps/pwa/public/favicon.ico` | ICO | 16+32+48 multi-res | Legacy browser favicon | `<link rel="icon" type="image/x-icon">` in index.html |
|
||||
| `apps/pwa/public/icon-192.png` | PNG (RGBA) | 192×192 | PWA manifest any icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/icon-512.png` | PNG (RGBA) | 512×512 | PWA manifest any icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/icon-512-maskable.png` | PNG (RGBA) | 512×512 | PWA manifest maskable icon | vite.config.ts manifest `icons` |
|
||||
| `apps/pwa/public/apple-touch-icon.png` | PNG (RGB, white bg) | 180×180 | iOS home screen icon | `<link rel="apple-touch-icon">` in index.html |
|
||||
| `apps/pwa/scripts/export-icons.ts` | TypeScript | — | Build-time raster export pipeline | `make icons` or equivalent |
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | none | not applicable |
|
||||
| Third-party | none | not applicable |
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
| --------------- | ----------- | -------------- |
|
||||
| shadcn official | none | not applicable |
|
||||
| Third-party | none | not applicable |
|
||||
|
||||
No third-party component registries used. All components are hand-rolled using the existing token layer.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user