@maccesar/titools 2.7.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -13
- package/lib/config.js +1 -1
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +25 -0
- package/skills/purgetss/references/EXAMPLES.md +86 -24
- package/skills/purgetss/references/app-branding.md +412 -0
- package/skills/purgetss/references/appearance-module.md +161 -0
- package/skills/purgetss/references/apply-directive.md +87 -31
- package/skills/purgetss/references/arbitrary-values.md +4 -0
- package/skills/purgetss/references/class-categories.md +10 -7
- package/skills/purgetss/references/class-index.md +25 -18
- package/skills/purgetss/references/cli-commands.md +219 -8
- package/skills/purgetss/references/configurable-properties.md +11 -7
- package/skills/purgetss/references/custom-rules.md +7 -3
- package/skills/purgetss/references/customization-deep-dive.md +52 -4
- package/skills/purgetss/references/dynamic-component-creation.md +29 -14
- package/skills/purgetss/references/grid-layout.md +20 -8
- package/skills/purgetss/references/icon-fonts.md +4 -0
- package/skills/purgetss/references/installation-setup.md +3 -8
- package/skills/purgetss/references/ios-large-titles.md +141 -0
- package/skills/purgetss/references/migration-guide.md +162 -25
- package/skills/purgetss/references/multi-density-images.md +363 -0
- package/skills/purgetss/references/opacity-modifier.md +4 -0
- package/skills/purgetss/references/performance-tips.md +5 -0
- package/skills/purgetss/references/platform-modifiers.md +4 -0
- package/skills/purgetss/references/semantic-colors.md +386 -0
- package/skills/purgetss/references/smart-mappings.md +50 -28
- package/skills/purgetss/references/tikit-components.md +3 -1
- package/skills/purgetss/references/titanium-resets.md +46 -15
- package/skills/purgetss/references/ui-ux-design.md +32 -6
- package/skills/ti-branding/SKILL.md +0 -230
- package/skills/ti-branding/assets/ic_launcher.xml +0 -6
- package/skills/ti-branding/references/android-adaptive-icons.md +0 -85
- package/skills/ti-branding/references/cleanup-legacy.md +0 -112
- package/skills/ti-branding/references/ios-appiconset.md +0 -62
- package/skills/ti-branding/references/master-input-guidelines.md +0 -84
- package/skills/ti-branding/references/notification-icons.md +0 -63
- package/skills/ti-branding/references/splash-screen-api.md +0 -81
- package/skills/ti-branding/references/ti-icon-paths.md +0 -84
- package/skills/ti-branding/references/tiapp-xml-snippets.md +0 -92
- package/skills/ti-branding/scripts/lib/cleanup-legacy.sh +0 -230
- package/skills/ti-branding/scripts/lib/deps.sh +0 -58
- package/skills/ti-branding/scripts/lib/gen-android-adaptive.sh +0 -64
- package/skills/ti-branding/scripts/lib/gen-android-legacy.sh +0 -36
- package/skills/ti-branding/scripts/lib/gen-ios.sh +0 -46
- package/skills/ti-branding/scripts/lib/gen-marketplace.sh +0 -55
- package/skills/ti-branding/scripts/lib/gen-notification.sh +0 -46
- package/skills/ti-branding/scripts/lib/gen-splash-icon.sh +0 -31
- package/skills/ti-branding/scripts/lib/prepare-master.sh +0 -48
- package/skills/ti-branding/scripts/lib/validate.sh +0 -67
- package/skills/ti-branding/scripts/ti-branding +0 -546
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# App Icons & Branding
|
|
2
|
+
|
|
3
|
+
The `purgetss brand` command (shipped in PurgeTSS v7.6.0) generates the complete Titanium branding set from a single SVG or PNG logo: launcher icons across every Android density, the adaptive-icon trio (foreground + background + monochrome), iOS 18+ Dark and Tinted variants, marketplace artwork, and optional notification/splash icons. Alloy and Classic layouts are auto-detected.
|
|
4
|
+
|
|
5
|
+
For the terse flag reference, see the [`brand` command reference](./cli-commands.md#brand-command). For sibling UI assets, see [Multi-Density Images](./multi-density-images.md).
|
|
6
|
+
|
|
7
|
+
> **INFO**
|
|
8
|
+
>
|
|
9
|
+
> The `brand` command at a glance
|
|
10
|
+
> `purgetss brand` turns one logo into every asset a Titanium app needs to ship — launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, and optional notification/splash icons. Works on both Alloy and Classic projects.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
Drop a logo file into `purgetss/brand/`, then run the command. That's the whole workflow.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mkdir -p purgetss/brand # if the folder doesn't exist yet
|
|
18
|
+
cp docs/my-logo.svg purgetss/brand/logo.svg
|
|
19
|
+
|
|
20
|
+
purgetss brand
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
On a first run the command:
|
|
24
|
+
|
|
25
|
+
1. Creates the `brand:` section in `purgetss/config.cjs` with sensible defaults (if missing).
|
|
26
|
+
2. Generates every branding file directly into the project (in-place).
|
|
27
|
+
3. Prints a compact summary of what was written.
|
|
28
|
+
|
|
29
|
+
Pass `--dry-run` to preview without writing any files:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
purgetss brand --dry-run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The `purgetss/brand/` convention
|
|
36
|
+
|
|
37
|
+
`init` creates `purgetss/brand/` (alongside `fonts/` and `images/`) so the folder is already there the first time you look for it, even before you've dropped in a logo.
|
|
38
|
+
|
|
39
|
+
PurgeTSS auto-discovers logo files under this folder, the same way `purgetss/fonts/` works for fonts. Drop files with these names and you're done — no flags, no config:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
purgetss/brand/
|
|
43
|
+
├── logo.svg required — main logo (or logo.png)
|
|
44
|
+
├── logo-mono.svg optional — monochrome layer + notifications
|
|
45
|
+
├── logo-dark.svg optional — iOS 18+ dark variant
|
|
46
|
+
└── logo-tinted.svg optional — iOS 18+ tinted variant
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Only `logo.svg` (or `logo.png`) is required. The other three are **opt-in refinements**:
|
|
50
|
+
|
|
51
|
+
| File | Required? | What it's for | Fallback when omitted |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| `logo.svg` / `logo.png` | **Required** | Main colored logo. Feeds every density and variant. | — |
|
|
54
|
+
| `logo-mono.svg` / `.png` | Optional | Android adaptive monochrome layer (themed icons on Android 13+) and notification icons. | Main logo is whitened automatically. |
|
|
55
|
+
| `logo-dark.svg` / `.png` | Optional | iOS 18+ Dark appearance variant. | Main logo on a transparent background (Apple HIG default). |
|
|
56
|
+
| `logo-tinted.svg` / `.png` | Optional | iOS 18+ Tinted appearance variant. | Grayscale of the main logo on black. |
|
|
57
|
+
|
|
58
|
+
Provide a dedicated `logo-mono` when the colored logo has detail that would collapse into a featureless blob under naive whitening (e.g. a painter's palette with colored dots — the monochrome variant should have cutouts instead).
|
|
59
|
+
|
|
60
|
+
Provide a dedicated `logo-dark` when dark-mode brand guidelines use a different lockup or color treatment; provide `logo-tinted` when you want a pre-simplified silhouette that tints better than a naive grayscale of the colored version.
|
|
61
|
+
|
|
62
|
+
> **INFO**
|
|
63
|
+
>
|
|
64
|
+
> Prefer SVG for the master
|
|
65
|
+
> SVG scales losslessly to every density Sharp needs to emit. A single `logo.svg` rasterizes perfectly at every `res-*dpi` output. PNG masters should be at least **1024×1024** to avoid upscaling artifacts.
|
|
66
|
+
|
|
67
|
+
### Overriding auto-discovery
|
|
68
|
+
|
|
69
|
+
You can also pass a path directly or point to a logo from the config. Useful when your masters live in `docs/` or another workflow folder:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
purgetss brand ./docs/snap-logo.svg
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or in `purgetss/config.cjs`:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
brand: {
|
|
79
|
+
logo: './docs/snap-logo.svg',
|
|
80
|
+
monochromeLogo: './docs/snap-logo-mono.svg'
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Precedence: **CLI flags win over config values, and config values win over auto-discovery.**
|
|
85
|
+
|
|
86
|
+
## The `brand:` config section
|
|
87
|
+
|
|
88
|
+
On the first run, `purgetss brand` injects a `brand:` block into your existing `purgetss/config.cjs` (between `purge:` and `theme:`) with these defaults:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
brand: {
|
|
92
|
+
splash: false, // also generate splash_icon.png × 5
|
|
93
|
+
padding: '15%', // Android safe-zone. Range: 12% tight (mature logos) — 20% conservative. Spec floor 19.44%.
|
|
94
|
+
iosPadding: '4%', // iOS aesthetic. Range: 2% bold — 8% conservative. No launcher mask.
|
|
95
|
+
darkBgColor: null, // opaque dark bg for DefaultIcon-Dark.png (null = transparent per Apple HIG)
|
|
96
|
+
bgColor: '#FFFFFF', // Android adaptive bg + iOS/marketplace flatten
|
|
97
|
+
notification: false, // also generate ic_stat_notify.png × 5
|
|
98
|
+
confirmOverwrites: true // prompt before overwriting files (set false to skip)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Key | Default | Purpose |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `splash` | `false` | When `true`, also emits `splash_icon.png` × 5 densities. |
|
|
105
|
+
| `padding` | `'15%'` | Per-side safe-zone padding inside the 1024×1024 Android canvas. |
|
|
106
|
+
| `iosPadding` | `'4%'` | Aesthetic padding for the iOS square icon. Smaller than Android because iOS has no launcher mask. |
|
|
107
|
+
| `darkBgColor` | `null` | Opaque background baked into `DefaultIcon-Dark.png`. `null` = transparent per Apple HIG (iOS paints its own gradient). |
|
|
108
|
+
| `bgColor` | `'#FFFFFF'` | Triple-purpose (see [Brand color](#brand-color) below). |
|
|
109
|
+
| `notification` | `false` | When `true`, also emits `ic_stat_notify.png` × 5 densities. |
|
|
110
|
+
| `confirmOverwrites` | `true` | When `false`, the `[y/N/a]` prompt is skipped. |
|
|
111
|
+
|
|
112
|
+
Change whatever you want to override globally; CLI flags still win for one-off runs.
|
|
113
|
+
|
|
114
|
+
## Overwrite confirmation
|
|
115
|
+
|
|
116
|
+
`brand` writes directly into the project, so it asks before overwriting anything:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
In-place mode will OVERWRITE files in <project>. Commit first if you want a rollback.
|
|
120
|
+
Continue? [y/N/a]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- `y` / `yes` — write this time
|
|
124
|
+
- `N` / `no` / `Enter` — abort (nothing is written)
|
|
125
|
+
- `a` / `always` — write, then add `confirmOverwrites: false` to the `brand:` section of `config.cjs` so the prompt stays quiet on future runs
|
|
126
|
+
|
|
127
|
+
The prompt is skipped automatically when:
|
|
128
|
+
|
|
129
|
+
- `stdin` is not a TTY (the `alloy.jmk` hook, CI, a pipe)
|
|
130
|
+
- you pass `-y` / `--yes` — one-shot bypass
|
|
131
|
+
- `PURGETSS_YES=1` is set in the environment — lasts the whole shell session
|
|
132
|
+
- `confirmOverwrites: false` is already in the `brand:` config
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
purgetss brand -y # skip prompt once
|
|
136
|
+
PURGETSS_YES=1 purgetss brand # skip for the whole session
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## What gets generated
|
|
140
|
+
|
|
141
|
+
The output is automatically routed to the right directory for your project layout.
|
|
142
|
+
|
|
143
|
+
**Alloy layout:**
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
<project>/
|
|
147
|
+
├── DefaultIcon.png ← 1024×1024, universal fallback (Android-safe padding)
|
|
148
|
+
├── DefaultIcon-ios.png ← 1024×1024, iOS flattened on bgColor
|
|
149
|
+
├── DefaultIcon-Dark.png ← 1024×1024, iOS 18+ dark (transparent per Apple HIG)
|
|
150
|
+
├── DefaultIcon-Tinted.png ← 1024×1024, iOS 18+ tinted (grayscale on black)
|
|
151
|
+
├── iTunesConnect.png ← 1024×1024, App Store submission
|
|
152
|
+
├── MarketplaceArtwork.png ← 512×512, Google Play submission
|
|
153
|
+
└── app/
|
|
154
|
+
└── assets/android/res/
|
|
155
|
+
├── mipmap-mdpi/ ← 108×108 foreground + background + monochrome + legacy
|
|
156
|
+
├── mipmap-hdpi/ ← 162×162
|
|
157
|
+
├── mipmap-xhdpi/ ← 216×216
|
|
158
|
+
├── mipmap-xxhdpi/ ← 324×324
|
|
159
|
+
├── mipmap-xxxhdpi/ ← 432×432
|
|
160
|
+
└── mipmap-anydpi-v26/
|
|
161
|
+
└── ic_launcher.xml ← adaptive-icon binder
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Classic layout:**
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
<project>/
|
|
168
|
+
├── DefaultIcon.png DefaultIcon-ios.png ... ← same root-level files as Alloy
|
|
169
|
+
└── platform/
|
|
170
|
+
└── android/res/
|
|
171
|
+
├── mipmap-*/ ← same 5 densities as Alloy
|
|
172
|
+
└── mipmap-anydpi-v26/ic_launcher.xml
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Android dark mode
|
|
176
|
+
|
|
177
|
+
> **INFO**
|
|
178
|
+
>
|
|
179
|
+
> No separate "dark icon" file on Android
|
|
180
|
+
> Unlike iOS 18+, Android has no dedicated dark-mode icon file. Instead, Android 13+ uses the **monochrome layer** of the adaptive icon and tints it based on the user's wallpaper + theme.
|
|
181
|
+
>
|
|
182
|
+
> The `brand` command generates `ic_launcher_monochrome.png` at every density by default — you don't need any extra flags to get themed icon support.
|
|
183
|
+
|
|
184
|
+
If you want to provide a dedicated silhouette (recommended for detailed logos):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
cp docs/my-logo-mono.svg purgetss/brand/logo-mono.svg
|
|
188
|
+
purgetss brand
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The monochrome layer is pure white (`RGB 255,255,255`) with alpha preserved. Android applies the user's tint on top at render time.
|
|
192
|
+
|
|
193
|
+
## iOS 18+ Dark and Tinted variants
|
|
194
|
+
|
|
195
|
+
iOS 18 added two appearance variants on top of the standard app icon: **Dark** (for the dark appearance of the Home Screen) and **Tinted** (for the user-accent-colored mode).
|
|
196
|
+
|
|
197
|
+
The `brand` command generates both by default:
|
|
198
|
+
|
|
199
|
+
- **`DefaultIcon-Dark.png`**: 1024×1024, **transparent by default** per Apple HIG. The system paints its own dark gradient behind the icon at render time. Override with `--dark-bg-color <hex>` to bake in an opaque dark tint instead.
|
|
200
|
+
- **`DefaultIcon-Tinted.png`**: 1024×1024, **grayscale on black (`#000000`)** per Apple HIG. iOS composites its own gradient background and multiplies the luminance by the user-selected accent color at render time.
|
|
201
|
+
|
|
202
|
+
### Skipping Dark or Tinted
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
purgetss brand --no-dark
|
|
206
|
+
purgetss brand --no-tinted
|
|
207
|
+
purgetss brand --no-dark --no-tinted
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Titanium SDK wiring status
|
|
211
|
+
|
|
212
|
+
> **WARNING**
|
|
213
|
+
>
|
|
214
|
+
> Upstream work in progress
|
|
215
|
+
> As of April 2026, Titanium SDK picks up `DefaultIcon-ios.png` automatically but does **not** yet wire `DefaultIcon-Dark.png` / `DefaultIcon-Tinted.png` into the generated iOS appiconset. Upstream tracking: [tidev/titanium-sdk#14122](https://github.com/tidev/titanium-sdk/issues/14122).
|
|
216
|
+
>
|
|
217
|
+
> Until that PR lands, after your first iOS build you may need to add the two PNGs manually into `build/iphone/Assets.xcassets/AppIcon.appiconset/` in Xcode (via the "Appearance" column in the asset catalog editor). Once #14122 merges, the command becomes fully end-to-end.
|
|
218
|
+
|
|
219
|
+
## Brand color
|
|
220
|
+
|
|
221
|
+
The `--bg-color` flag (or `brand.bgColor` in config) controls three things at once:
|
|
222
|
+
|
|
223
|
+
1. The **Android adaptive background layer**: a solid color that fills the full 108dp canvas behind your logo.
|
|
224
|
+
2. The **iOS alpha flatten** for `DefaultIcon-ios.png`. Apple rejects transparent App Store icons, so the logo is flattened on this color.
|
|
225
|
+
3. The **marketplace flatten** for `iTunesConnect.png` and `MarketplaceArtwork.png` when you pass a non-default value explicitly.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
purgetss brand --bg-color "#0B1326"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
If you never pass the flag, background stays `#FFFFFF` and the marketplace artwork keeps its alpha channel (matches Titanium's default).
|
|
232
|
+
|
|
233
|
+
## Padding guidance
|
|
234
|
+
|
|
235
|
+
`--padding` controls how much safe-zone padding (per side, expressed as a percentage) surrounds the logo inside the Android adaptive canvas. The default `15%` matches real-world production apps like Gmail and Chrome.
|
|
236
|
+
|
|
237
|
+
| Padding | Logo fill | When to use |
|
|
238
|
+
| ------- | --------- | ------------------------------------------------------------------------------------------------------ |
|
|
239
|
+
| `12%` | 76% | Logos with built-in breathing room (circular wordmarks, letterforms inside a shape). Tight, bold look. |
|
|
240
|
+
| `15%` | 70% | **Default**. Balanced, safe on Pixel, Oppo, Samsung, OneUI, and Nova. |
|
|
241
|
+
| `18%` | 64% | Defensive: for intricate logos, fine serifs, multi-element designs. |
|
|
242
|
+
| `20%` | 60% | Conservative, spec-compliant. Safe on every launcher, including legacy aggressive masks. |
|
|
243
|
+
|
|
244
|
+
A useful visual check is the "corners" heuristic: imagine a circle inscribed in your 1024×1024 canvas with the given padding. If your logo's outermost corners fit inside that circle, you're safe on circular launchers (Pixel default, Oppo Android 15). If they poke out, they'll be clipped.
|
|
245
|
+
|
|
246
|
+
The official Android spec floor is `19.44%` (108dp canvas, 66dp inscribed safe-zone circle). Modern launchers are more permissive: Gmail and Chrome ship at 10–14%, which is why `15%` is the default. The 19.44% floor is the theoretical worst-case for aggressive masks; if you're worried about legacy Samsung teardrop masks or similar, bump to `--padding 20` to stay inside the spec.
|
|
247
|
+
|
|
248
|
+
`--ios-padding` is a separate lever — iOS has no launcher mask, so the range is different:
|
|
249
|
+
|
|
250
|
+
| iOS padding | When to use |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| `2%` | Bold, edge-to-edge logos. |
|
|
253
|
+
| `4%` | **Default**. Balanced aesthetic. |
|
|
254
|
+
| `8%` | Conservative, generous whitespace around the mark. |
|
|
255
|
+
|
|
256
|
+
## Cleanup legacy branding artifacts
|
|
257
|
+
|
|
258
|
+
Projects that predate Android adaptive icons (API 26+) or modern iOS launch storyboards often accumulate obsolete assets: `res-long-*/res-notlong-*` qualifiers dead since Android 3.0, legacy `Default-*.png` launch images ignored when the storyboard is enabled, pre-adaptive `appicon.png`, and so on.
|
|
259
|
+
|
|
260
|
+
The `--cleanup-legacy` flag removes them with context-aware safety rules: it reads `tiapp.xml` to decide what's safe to delete based on your project's configuration. Always preview first:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
purgetss brand --cleanup-legacy --dry-run
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Review the output, then remove `--dry-run` to apply:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
purgetss brand --cleanup-legacy
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Add `--aggressive` to also remove `ldpi` density folders (less than 1% of active Android devices globally in 2026):
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
purgetss brand --cleanup-legacy --aggressive
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
> **DANGER**
|
|
279
|
+
>
|
|
280
|
+
> Commit first
|
|
281
|
+
> `--cleanup-legacy` deletes files permanently. Commit your project to git before running without `--dry-run` so `git restore` is available as a rollback.
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
### The icon looks cropped or cramped on my phone
|
|
286
|
+
|
|
287
|
+
Your logo is probably landing too close to the launcher mask. Increase `--padding`:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
purgetss brand --padding 20
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Or set it in the config:
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
brand: { padding: '20%' }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### The icon looks tiny / lost in the middle
|
|
300
|
+
|
|
301
|
+
Padding is too generous. Lower it:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
purgetss brand --padding 12
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### The monochrome version looks like a white blob
|
|
308
|
+
|
|
309
|
+
Your colored logo likely has multi-color detail that doesn't survive a naive whitening. Provide a dedicated silhouette:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
cp docs/my-logo-mono.svg purgetss/brand/logo-mono.svg
|
|
313
|
+
purgetss brand
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### iOS rejects the app icon upload ("contains transparency")
|
|
317
|
+
|
|
318
|
+
That's Apple's rule: App Store icons must have no alpha channel. `DefaultIcon-ios.png` is always flattened on `bgColor` for that reason. If you edited the file manually and reintroduced alpha, re-run `purgetss brand` to regenerate.
|
|
319
|
+
|
|
320
|
+
### The dark variant doesn't show on my iPhone
|
|
321
|
+
|
|
322
|
+
Dark variants require iOS 18+ and Titanium SDK automatic wiring (tracked upstream in [titanium-sdk#14122](https://github.com/tidev/titanium-sdk/issues/14122)). Until that PR merges, you may need to add `DefaultIcon-Dark.png` and `DefaultIcon-Tinted.png` manually into the Xcode appiconset after the first iOS build.
|
|
323
|
+
|
|
324
|
+
### I get "Input image exceeds pixel limit" on an SVG from Affinity / Illustrator
|
|
325
|
+
|
|
326
|
+
Affinity Designer and Adobe Illustrator often bake transforms into the exported SVG's `viewBox`, so the intrinsic dimensions end up at something absurd like `29559×13542 pt`. Rasterized at 1× density, that blows past Sharp's pixel limit and the command crashes.
|
|
327
|
+
|
|
328
|
+
PurgeTSS checks the `viewBox` on every SVG. When either side is over `4096 pt`, it prints a warning with the actual dimensions and switches to an adaptive density that caps the output pixel count regardless of input size. The warning tells you the source is oversized; the command still finishes.
|
|
329
|
+
|
|
330
|
+
If you want to clean up the source, re-export from the vector editor with a canvas-sized viewBox (`0 0 1024 1024`, for example). The rasterized output is identical either way, but a normalized viewBox keeps the SVG portable for other tools.
|
|
331
|
+
|
|
332
|
+
### I changed my bg color — do I need to regenerate the Android densities too?
|
|
333
|
+
|
|
334
|
+
Yes. `bgColor` bakes into every Android background layer and the iOS flatten. Re-run:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
purgetss brand --bg-color "#NEW_COLOR"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
All 5 Android densities, marketplace artwork, and iOS variants regenerate in one pass.
|
|
341
|
+
|
|
342
|
+
## Full flag reference
|
|
343
|
+
|
|
344
|
+
**Project & output**
|
|
345
|
+
|
|
346
|
+
| Flag | Purpose |
|
|
347
|
+
| --- | --- |
|
|
348
|
+
| `--project <path>` | Project root (defaults to cwd). |
|
|
349
|
+
| `--dry-run` | Preview what would be generated without writing any files. |
|
|
350
|
+
| `--output <dir>` | Stage into `<dir>` instead of writing in place. |
|
|
351
|
+
| `-y`, `--yes` | Skip the overwrite confirmation prompt for this invocation. |
|
|
352
|
+
|
|
353
|
+
**Visual customization**
|
|
354
|
+
|
|
355
|
+
| Flag | Purpose |
|
|
356
|
+
| --- | --- |
|
|
357
|
+
| `--bg-color <hex>` | Background color for Android adaptive + iOS/marketplace flatten. |
|
|
358
|
+
| `--padding <n>` | Android safe-zone % (range `12–20`, default `15`). |
|
|
359
|
+
| `--ios-padding <n>` | iOS aesthetic padding % (range `2–8`, default `4`). |
|
|
360
|
+
|
|
361
|
+
**Optional asset types**
|
|
362
|
+
|
|
363
|
+
| Flag | Purpose |
|
|
364
|
+
| --- | --- |
|
|
365
|
+
| `--notification` | Also emit `ic_stat_notify.png × 5`. |
|
|
366
|
+
| `--splash` | Also emit `splash_icon.png × 5`. |
|
|
367
|
+
|
|
368
|
+
**Logo variants & overrides**
|
|
369
|
+
|
|
370
|
+
| Flag | Purpose |
|
|
371
|
+
| --- | --- |
|
|
372
|
+
| `--monochrome-logo <path>` | Override `purgetss/brand/logo-mono.{svg,png}`. |
|
|
373
|
+
| `--dark-logo <path>` | Override `purgetss/brand/logo-dark.{svg,png}`. |
|
|
374
|
+
| `--dark-bg-color <hex>` | Opaque dark bg for `DefaultIcon-Dark.png` (default: transparent per Apple HIG). |
|
|
375
|
+
| `--tinted-logo <path>` | Override `purgetss/brand/logo-tinted.{svg,png}`. |
|
|
376
|
+
| `--no-dark` | Skip `DefaultIcon-Dark.png`. |
|
|
377
|
+
| `--no-tinted` | Skip `DefaultIcon-Tinted.png`. |
|
|
378
|
+
|
|
379
|
+
**Legacy cleanup**
|
|
380
|
+
|
|
381
|
+
| Flag | Purpose |
|
|
382
|
+
| --- | --- |
|
|
383
|
+
| `--cleanup-legacy` | Remove obsolete branding artifacts (reads `tiapp.xml` for safety rules). |
|
|
384
|
+
| `--aggressive` | With `--cleanup-legacy`, also remove `ldpi` density folders. |
|
|
385
|
+
|
|
386
|
+
**Diagnostics**
|
|
387
|
+
|
|
388
|
+
| Flag | Purpose |
|
|
389
|
+
| --- | --- |
|
|
390
|
+
| `--notes` | Print full `tiapp.xml` snippets + padding tuning guide. |
|
|
391
|
+
| `--debug` | Print extra diagnostics. |
|
|
392
|
+
|
|
393
|
+
### Examples
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
purgetss brand # uses purgetss/brand/logo.svg + config
|
|
397
|
+
purgetss brand --bg-color "#0B1326" # override bg color
|
|
398
|
+
purgetss brand --notification --splash # add notification + splash
|
|
399
|
+
purgetss brand --no-tinted # skip iOS 18+ tinted variant
|
|
400
|
+
purgetss brand --dry-run # preview without writing
|
|
401
|
+
purgetss brand --cleanup-legacy --dry-run # preview legacy cleanup
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Community-Discovered Patterns
|
|
405
|
+
|
|
406
|
+
- **Titanium SDK wiring lag for iOS 18+ variants.** Titanium SDK currently wires `DefaultIcon-ios.png` into the generated appiconset automatically, but `DefaultIcon-Dark.png` and `DefaultIcon-Tinted.png` are not picked up yet. Until [tidev/titanium-sdk#14122](https://github.com/tidev/titanium-sdk/issues/14122) merges, the practical workaround is: run `ti build -p ios` once, then open `build/iphone/Assets.xcassets/AppIcon.appiconset/` in Xcode and drag the two PNGs into the "Appearance" column of the asset catalog editor. The generated PNGs are already named and sized correctly — no resizing needed.
|
|
407
|
+
- **Modern launchers ship tighter than the spec.** The Android adaptive-icon spec floor is `19.44%` padding, but shipped apps like Gmail and Chrome use `10–14%`. PurgeTSS's `15%` default sits in that real-world range rather than the conservative spec floor, which is why icons look tight-but-balanced out of the box.
|
|
408
|
+
|
|
409
|
+
## See also
|
|
410
|
+
|
|
411
|
+
- [`brand` command reference](./cli-commands.md#brand-command) — terse flag list.
|
|
412
|
+
- [Multi-Density Images](./multi-density-images.md) — sibling `images` command for UI assets.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Appearance Module
|
|
2
|
+
|
|
3
|
+
The `Appearance` export from `purgetss.ui` (added in **v7.5.3**) handles Light / Dark / System mode switching and persists the user's choice across app restarts. It is a thin, deterministic wrapper around Titanium's native `Ti.UI.overrideUserInterfaceStyle` plus `Ti.App.Properties`, exposed as a singleton so every controller in the app reads and writes the same state.
|
|
4
|
+
|
|
5
|
+
This file covers the four public methods, the required setup, a full Settings-view example, and the lifecycle that keeps the saved mode in sync with the UI. For the color wiring that actually makes the UI *respond* to mode changes, see [semantic-colors.md](./semantic-colors.md).
|
|
6
|
+
|
|
7
|
+
> **INFO**
|
|
8
|
+
>
|
|
9
|
+
> Requires PurgeTSS **>= 7.5.3**. On older versions the `Appearance` export does not exist — the `require('purgetss.ui')` call will succeed, but `Appearance` will be `undefined`. Run `purgetss --version` in the project root to confirm.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Call `Appearance.init()` **once** at app startup, before opening the first window. The standard place is the top of `app/controllers/index.js`.
|
|
14
|
+
|
|
15
|
+
`app/controllers/index.js`
|
|
16
|
+
```js
|
|
17
|
+
const { Appearance } = require('purgetss.ui')
|
|
18
|
+
|
|
19
|
+
Appearance.init()
|
|
20
|
+
|
|
21
|
+
$.navWin.open()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`init()` reads the saved preference from `Ti.App.Properties` (key: `userInterfaceStyle`) and applies it through `Ti.UI.overrideUserInterfaceStyle`. If nothing has been saved yet, the system default is used, and semantic colors resolve against whatever the OS reports.
|
|
25
|
+
|
|
26
|
+
> **WARNING**
|
|
27
|
+
>
|
|
28
|
+
> Call `Appearance.init()` **before** `$.navWin.open()` (or whichever window you open first). If the window opens first, it will render once against the system default and then flicker to the saved mode when `init()` runs — a visible flash on cold launch.
|
|
29
|
+
|
|
30
|
+
## Methods
|
|
31
|
+
|
|
32
|
+
| Method | Description |
|
|
33
|
+
| ------------ | --------------------------------------------------------------------------- |
|
|
34
|
+
| `init()` | Restore the saved mode from `Ti.App.Properties` and apply it via `Ti.UI.overrideUserInterfaceStyle`. Call once at app startup before opening the first window. |
|
|
35
|
+
| `set(mode)` | Apply and persist a mode. Accepts `'system'`, `'light'`, or `'dark'`. Any other value is silently ignored. |
|
|
36
|
+
| `get()` | Returns the current mode as a string: `'system'`, `'light'`, or `'dark'`. |
|
|
37
|
+
| `toggle()` | Switch between `'light'` and `'dark'`. Skips `'system'` — if current mode is `'system'` the next call produces `'dark'`. |
|
|
38
|
+
|
|
39
|
+
All four methods are **synchronous**. They do not fire events; if you need to react to a mode change, call your update logic right after `Appearance.set(...)`.
|
|
40
|
+
|
|
41
|
+
## Full Settings-view example
|
|
42
|
+
|
|
43
|
+
A three-row settings panel — System / Light / Dark — where each row shows a `fa-solid fa-circle-check` icon next to the currently active mode. Tapping a row calls `Appearance.set(...)` and updates the check icons.
|
|
44
|
+
|
|
45
|
+
### View
|
|
46
|
+
|
|
47
|
+
`app/views/settings.xml`
|
|
48
|
+
```xml
|
|
49
|
+
<Alloy>
|
|
50
|
+
<Window class="bg-surface title-attributes-on-surface bar-surface nav-tint-accent" title="Settings">
|
|
51
|
+
<ScrollView class="vertical content-w-screen content-h-auto">
|
|
52
|
+
|
|
53
|
+
<Label class="mx-4 mb-2 mt-6 h-auto text-xs font-semibold text-on-surface-variant">APPEARANCE</Label>
|
|
54
|
+
|
|
55
|
+
<View class="mx-4 mb-4 h-auto w-screen rounded-xl bg-surface-high shadow-sm vertical clip-enabled">
|
|
56
|
+
|
|
57
|
+
<!-- System -->
|
|
58
|
+
<View class="horizontal mx-4 h-12 w-screen" onClick="selectSystem">
|
|
59
|
+
<Label class="fa-solid fa-mobile-screen ml-0 h-12 w-8 text-blue-500" />
|
|
60
|
+
<Label class="h-12 text-sm font-semibold text-on-surface">System</Label>
|
|
61
|
+
<Label id="themeSystemCheck" class="fa-solid fa-circle-check mr-0 h-12 w-screen text-right text-green-500" />
|
|
62
|
+
</View>
|
|
63
|
+
|
|
64
|
+
<View class="h-px w-screen bg-border" />
|
|
65
|
+
|
|
66
|
+
<!-- Light -->
|
|
67
|
+
<View class="horizontal mx-4 h-12 w-screen" onClick="selectLight">
|
|
68
|
+
<Label class="fa-solid fa-sun ml-0 h-12 w-8 text-amber-500" />
|
|
69
|
+
<Label class="h-12 text-sm font-semibold text-on-surface">Light</Label>
|
|
70
|
+
<Label id="themeLightCheck" class="fa-solid fa-circle-check mr-0 hidden h-12 w-screen text-right text-green-500" />
|
|
71
|
+
</View>
|
|
72
|
+
|
|
73
|
+
<View class="h-px w-screen bg-border" />
|
|
74
|
+
|
|
75
|
+
<!-- Dark -->
|
|
76
|
+
<View class="horizontal mx-4 h-12 w-screen" onClick="selectDark">
|
|
77
|
+
<Label class="fa-solid fa-moon ml-0 h-12 w-8 text-purple-500" />
|
|
78
|
+
<Label class="h-12 text-sm font-semibold text-on-surface">Dark</Label>
|
|
79
|
+
<Label id="themeDarkCheck" class="fa-solid fa-circle-check mr-0 hidden h-12 w-screen text-right text-green-500" />
|
|
80
|
+
</View>
|
|
81
|
+
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
</ScrollView>
|
|
85
|
+
</Window>
|
|
86
|
+
</Alloy>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Controller
|
|
90
|
+
|
|
91
|
+
`app/controllers/settings.js`
|
|
92
|
+
```js
|
|
93
|
+
const { Appearance } = require('purgetss.ui')
|
|
94
|
+
|
|
95
|
+
// Sync check icons with the mode saved at app startup.
|
|
96
|
+
updateUI(Appearance.get())
|
|
97
|
+
|
|
98
|
+
const selectDark = () => selectAppearance('dark')
|
|
99
|
+
const selectLight = () => selectAppearance('light')
|
|
100
|
+
const selectSystem = () => selectAppearance('system')
|
|
101
|
+
|
|
102
|
+
const selectAppearance = (value) => {
|
|
103
|
+
Appearance.set(value)
|
|
104
|
+
updateUI(value)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const updateUI = (value) => {
|
|
108
|
+
$.themeDarkCheck.visible = (value === 'dark')
|
|
109
|
+
$.themeLightCheck.visible = (value === 'light')
|
|
110
|
+
$.themeSystemCheck.visible = (value === 'system')
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The background, text, border, and icon colors in the XML come from semantic color classes (`bg-surface`, `bg-surface-high`, `text-on-surface`, `bg-border`, etc.) — see [semantic-colors.md](./semantic-colors.md) for how to wire those up so the whole view flips when `Appearance.set('dark')` fires.
|
|
115
|
+
|
|
116
|
+
## Lifecycle
|
|
117
|
+
|
|
118
|
+
How the four moving parts — `Appearance.init()`, `Ti.App.Properties`, `Ti.UI.overrideUserInterfaceStyle`, and semantic colors — cooperate:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
app startup
|
|
122
|
+
|
|
|
123
|
+
+-- 1. Appearance.init()
|
|
124
|
+
| |
|
|
125
|
+
| +-- reads key 'userInterfaceStyle' from Ti.App.Properties
|
|
126
|
+
| +-- sets Ti.UI.overrideUserInterfaceStyle to the saved value
|
|
127
|
+
| +-- stores 'system' | 'light' | 'dark' in the singleton's currentMode
|
|
128
|
+
|
|
|
129
|
+
+-- 2. First window opens
|
|
130
|
+
| |
|
|
131
|
+
| +-- semantic colors resolve against the now-applied mode
|
|
132
|
+
| +-- bg-surface -> surfaceColor -> light or dark hex
|
|
133
|
+
| +-- text-on-surface -> textColor -> light or dark hex
|
|
134
|
+
|
|
|
135
|
+
+-- 3. User taps "Dark" in Settings
|
|
136
|
+
|
|
|
137
|
+
+-- Appearance.set('dark')
|
|
138
|
+
| |
|
|
139
|
+
| +-- Ti.UI.overrideUserInterfaceStyle = USER_INTERFACE_STYLE_DARK
|
|
140
|
+
| +-- Ti.App.Properties.setInt('userInterfaceStyle', ...)
|
|
141
|
+
|
|
|
142
|
+
+-- All semantic colors repaint instantly
|
|
143
|
+
+-- Preference survives the next cold launch
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`Ti.App.Properties` is the persistence layer — the value written during `set(...)` is what `init()` reads on the next launch. Clearing app storage or reinstalling resets to `'system'`.
|
|
147
|
+
|
|
148
|
+
> **INFO**
|
|
149
|
+
>
|
|
150
|
+
> `toggle()` intentionally skips `'system'`. If the current mode is `'system'`, calling `toggle()` produces `'dark'`. If you want a three-way cycle (System -> Light -> Dark -> System), build it in userland with `Appearance.get()` + `Appearance.set(...)`; do not expect `toggle()` to do it.
|
|
151
|
+
|
|
152
|
+
## Related
|
|
153
|
+
|
|
154
|
+
- [semantic-colors.md](./semantic-colors.md) — defining `app/assets/semantic.colors.json` and mapping the color names in `config.cjs` so views actually respond to mode changes.
|
|
155
|
+
- [cli-commands.md#semantic-command](./cli-commands.md#semantic-command) — the `purgetss semantic` command, which writes both the JSON and the `config.cjs` mapping for you.
|
|
156
|
+
- [customization-deep-dive.md](./customization-deep-dive.md) — full `config.cjs` structure, including the `theme.extend.colors` section where semantic names are registered.
|
|
157
|
+
|
|
158
|
+
## Community-Discovered Patterns
|
|
159
|
+
|
|
160
|
+
- **Always call `Appearance.init()` before opening the first window.** Opening the window first causes a visible flicker on cold launch as the UI repaints from the system default to the saved mode. The official best-practices guide puts `Appearance.init()` on line 3 of `app/controllers/index.js`, immediately before `$.navWin.open()`, for exactly this reason.
|
|
161
|
+
- **`toggle()` is not a three-state cycle.** It only alternates between `'light'` and `'dark'`. If the saved mode is `'system'`, the first `toggle()` call lands on `'dark'`. Build a cycle yourself with `get()` + `set(...)` when you need one.
|