@maccesar/titools 4.23.1 → 5.1.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.
Files changed (56) hide show
  1. package/README.md +26 -3
  2. package/package.json +1 -1
  3. package/skills/alloy-guides/references/MODELS.md +2 -2
  4. package/skills/alloy-guides/references/MODELS_ADVANCED.md +1 -1
  5. package/skills/alloy-guides/references/VIEWS_XML.md +13 -13
  6. package/skills/alloy-howtos/references/best_practices.md +2 -2
  7. package/skills/alloy-howtos/references/cli_reference.md +1 -1
  8. package/skills/alloy-howtos/references/debugging_troubleshooting.md +1 -1
  9. package/skills/purgetss/SKILL.md +27 -24
  10. package/skills/purgetss/references/EXAMPLES.md +52 -41
  11. package/skills/purgetss/references/adopting-purgetss.md +137 -0
  12. package/skills/purgetss/references/animation-advanced.md +18 -5
  13. package/skills/purgetss/references/animation-system.md +21 -9
  14. package/skills/purgetss/references/app-branding.md +36 -97
  15. package/skills/purgetss/references/appearance-module.md +70 -5
  16. package/skills/purgetss/references/apply-directive.md +14 -6
  17. package/skills/purgetss/references/arbitrary-values.md +59 -23
  18. package/skills/purgetss/references/brand-padding-geometry.md +105 -0
  19. package/skills/purgetss/references/class-categories.md +684 -272
  20. package/skills/purgetss/references/class-index-properties.md +713 -607
  21. package/skills/purgetss/references/class-index.md +149 -84
  22. package/skills/purgetss/references/classic-projects.md +2 -2
  23. package/skills/purgetss/references/cli-commands.md +27 -213
  24. package/skills/purgetss/references/color-commands.md +218 -0
  25. package/skills/purgetss/references/configurable-properties.md +17 -2
  26. package/skills/purgetss/references/custom-fonts.md +21 -16
  27. package/skills/purgetss/references/custom-rules.md +7 -7
  28. package/skills/purgetss/references/customization-deep-dive.md +14 -11
  29. package/skills/purgetss/references/dynamic-component-creation.md +104 -61
  30. package/skills/purgetss/references/grid-layout.md +21 -24
  31. package/skills/purgetss/references/icon-fonts.md +37 -20
  32. package/skills/purgetss/references/installation-setup.md +1 -1
  33. package/skills/purgetss/references/ios-large-titles.md +12 -11
  34. package/skills/purgetss/references/migration-guide.md +101 -10
  35. package/skills/purgetss/references/multi-density-images.md +44 -24
  36. package/skills/purgetss/references/opacity-modifier.md +1 -1
  37. package/skills/purgetss/references/performance-tips.md +5 -4
  38. package/skills/purgetss/references/platform-modifiers.md +2 -2
  39. package/skills/purgetss/references/purgetss-ui-classic.md +21 -5
  40. package/skills/purgetss/references/semantic-colors.md +20 -13
  41. package/skills/purgetss/references/smart-mappings.md +8 -7
  42. package/skills/purgetss/references/svg-pipeline.md +21 -13
  43. package/skills/purgetss/references/tikit-components.md +2 -2
  44. package/skills/purgetss/references/titanium-resets.md +27 -17
  45. package/skills/purgetss/references/ui-ux-design.md +38 -26
  46. package/skills/purgetss/references/values-and-units.md +1 -2
  47. package/skills/purgetss/references/version-history.md +32 -4
  48. package/skills/ti-api/references/api-data-network.md +1 -1
  49. package/skills/ti-api/references/api-media.md +1 -1
  50. package/skills/ti-api/references/api-services.md +8 -5
  51. package/skills/ti-api/references/api-ui-lists.md +3 -2
  52. package/skills/ti-api/references/api-ui-text-input.md +1 -1
  53. package/skills/ti-expert/references/feedback-surfaces.md +1 -1
  54. package/skills/ti-guides/references/resources.md +1 -1
  55. package/skills/ti-guides/references/sdk-release-notes.md +22 -11
  56. package/skills/ti-howtos/references/extending-titanium.md +77 -0
@@ -81,7 +81,7 @@ $.navWin.open()
81
81
 
82
82
  > **WARNING**
83
83
  >
84
- > 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.
84
+ > Call `Appearance.init()` **before** `$.navWin.open()` (or whichever window you open first). Both official pages place the call before the first window opens.
85
85
 
86
86
  ### 3. Build an Appearance toggle
87
87
 
@@ -158,7 +158,61 @@ function updateUI(value) {
158
158
 
159
159
  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.
160
160
 
161
- > **Why named functions in Alloy controllers?** Alloy resolves `onClick="selectDark"` by looking up a top-level identifier on the controller scope. Named function declarations (`function selectDark() { ... }`) hoist and are reliably resolvable; arrow functions assigned to `const` are not hoisted and tend to bite you when an XML attribute fires before the binding has executed. The official setup guide uses named functions for this reason.
161
+ Semantic colors apply anywhere you use `bg-*`, `text-*`, and `border-*` classes: Windows, Views, Labels, Buttons, TextFields, TextAreas, ListViews, and custom classes that reference a semantic name. One appearance switch updates the UI.
162
+
163
+ The official setup guide declares the `onClick` handlers (`selectDark`, `selectLight`, `selectSystem`) as named functions at the top level of the controller.
164
+
165
+ ### Minimal Alloy example
166
+
167
+ The API page uses a flat three-color mapping and a single toggle button:
168
+
169
+ `app/assets/semantic.colors.json`
170
+ ```json
171
+ {
172
+ "surfaceColor": { "light": "#F8FAFC", "dark": "#0F172A" },
173
+ "textColor": { "light": "#0F172A", "dark": "#F8FAFC" },
174
+ "accentColor": { "light": "#2563EB", "dark": "#60A5FA" }
175
+ }
176
+ ```
177
+
178
+ `purgetss/config.cjs`
179
+ ```js
180
+ module.exports = {
181
+ theme: {
182
+ extend: {
183
+ colors: {
184
+ surface: 'surfaceColor',
185
+ text: 'textColor',
186
+ accent: 'accentColor'
187
+ }
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ `app/views/index.xml`
194
+ ```xml
195
+ <Alloy>
196
+ <Window class="bg-surface">
197
+ <Label class="text-text" text="Appearance" />
198
+ <Button class="bg-accent text-white" title="Toggle" onClick="toggleAppearance" />
199
+ </Window>
200
+ </Alloy>
201
+ ```
202
+
203
+ `app/controllers/index.js`
204
+ ```js
205
+ const { Appearance } = require('purgetss.ui')
206
+
207
+ Appearance.init()
208
+
209
+ function toggleAppearance() {
210
+ Appearance.toggle()
211
+ Ti.API.info(`Mode: ${Appearance.get()}`)
212
+ }
213
+
214
+ $.index.open()
215
+ ```
162
216
 
163
217
  ## Titanium Classic
164
218
 
@@ -190,9 +244,18 @@ const toggleButton = Ti.UI.createButton({
190
244
  backgroundColor: 'accentColor'
191
245
  })
192
246
 
247
+ function updateModeLabel() {
248
+ title.text = `Mode: ${Appearance.get()}`
249
+ }
250
+
193
251
  function toggleAppearance() {
194
252
  Appearance.toggle()
195
- title.text = `Mode: ${Appearance.get()}`
253
+ updateModeLabel()
254
+ }
255
+
256
+ function useSystemAppearance() {
257
+ Appearance.set('system')
258
+ updateModeLabel()
196
259
  }
197
260
 
198
261
  function disposeWindow() {
@@ -205,6 +268,8 @@ window.add(title)
205
268
  window.add(toggleButton)
206
269
  window.addEventListener('close', disposeWindow)
207
270
  window.open()
271
+
272
+ // Call useSystemAppearance() from your own System mode control.
208
273
  ```
209
274
 
210
275
  Run a full native build after adding or changing semantic keys. A LiveView reload alone does not refresh Titanium's native color catalog. The `purgetss semantic` command can create the JSON for a Classic project; it writes only `Resources/semantic.colors.json`, not `config.cjs`, TSS, or utility mappings.
@@ -261,7 +326,7 @@ app startup
261
326
  ## Related
262
327
 
263
328
  - [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.
264
- - [cli-commands.md#semantic-command](./cli-commands.md#semantic-command) — the `purgetss semantic` command; Alloy also receives `config.cjs` mappings, while Classic receives only the native JSON.
329
+ - [color-commands.md#semantic-command](./color-commands.md#semantic-command) — the `purgetss semantic` command; Alloy also receives `config.cjs` mappings, while Classic receives only the native JSON.
265
330
  - [customization-deep-dive.md](./customization-deep-dive.md) — full `config.cjs` structure, including the `theme.extend.colors` section where semantic names are registered.
266
331
  - [purgetss-ui-classic.md](./purgetss-ui-classic.md) — generated module path, Classic animation factory, method contracts, and cleanup.
267
332
 
@@ -269,5 +334,5 @@ app startup
269
334
 
270
335
  These field-tested patterns are now also confirmed by the official Appearance documentation:
271
336
 
272
- - **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.
337
+ - **Always call `Appearance.init()` before opening the first window.** The official best-practices guide calls `Appearance.init()` right after the `require` in `app/controllers/index.js`, before `$.navWin.open()`. The docs give no reason beyond the ordering; the flicker on cold launch when the window opens first is a community observation.
273
338
  - **`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.
@@ -273,7 +273,7 @@ module.exports = {
273
273
  extend: {
274
274
  ImageView: {
275
275
  ios: {
276
- apply: 'hires-true'
276
+ apply: 'hires'
277
277
  }
278
278
  },
279
279
  View: {
@@ -467,19 +467,27 @@ Constants like `Ti.UI.iOS.CLIP_MODE_ENABLED` or `Ti.UI.iOS.StatusBar.LIGHT_CONTE
467
467
 
468
468
  **See the dedicated reference: [`ios-large-titles.md`](./ios-large-titles.md)** — it covers the full pattern (three-property pairing, global-defaults recipe, TabGroup implicit NavigationWindow behavior, detail-window opt-out, and the ScrollView `content-w-screen` / `content-h-auto` pairing).
469
469
 
470
- Minimal recap — when Large Titles are in use, set the base iOS Window defaults once via `apply` in `config.cjs` rather than repeating them in every XML view:
470
+ Minimal recap — when Large Titles are in use, set the base iOS Window defaults once via `apply` in `config.cjs` rather than repeating them in every XML view. Put it under `theme.extend.Window` so the framework's white `backgroundColor` default is kept (a top-level `theme.Window` is replace mode and drops it):
471
471
 
472
472
  `./purgetss/config.cjs`
473
473
  ```javascript
474
474
  module.exports = {
475
475
  theme: {
476
- Window: {
477
- ios: {
478
- apply: 'auto-adjust-scroll-view-insets extend-edges-all large-title-enabled'
476
+ extend: {
477
+ Window: {
478
+ ios: {
479
+ apply: 'auto-adjust-scroll-view-insets extend-edges-all large-title-enabled'
480
+ }
479
481
  }
480
482
  }
481
483
  }
482
484
  };
483
485
  ```
484
486
 
485
- The `ios:` block (not an inline `ios:` prefix) is required because these classes only exist with a `[platform=ios]` suffix — see "Platform-Specific Classes" above. The *why* (rendering delay vs content-behind-nav-bar), the display-mode constants, and per-window overrides live in [`ios-large-titles.md`](./ios-large-titles.md) and in the official PurgeTSS docs at [Best Practices → Large Titles on iOS](https://purgetss.com/docs/best-practices/3-large-titles-on-ios).
487
+ `./purgetss/styles/utilities.tss`
488
+ ```tss
489
+ 'Window': { backgroundColor: '#FFFFFF' }
490
+ 'Window[platform=ios]': { autoAdjustScrollViewInsets: true, extendEdges: [ Ti.UI.EXTEND_EDGE_ALL ], largeTitleEnabled: true }
491
+ ```
492
+
493
+ These three classes exist without a platform suffix in `utilities.tss`, so they would resolve from a `default` block too; the `ios:` block is what scopes the generated rule to `[platform=ios]` so Android never receives these properties. The *why* (rendering delay vs content-behind-nav-bar), the display-mode constants, and per-window overrides live in [`ios-large-titles.md`](./ios-large-titles.md) and in the official PurgeTSS docs at [Best Practices → Large Titles on iOS](https://purgetss.com/docs/best-practices/large-titles-on-ios).
@@ -5,7 +5,7 @@ When you need a one-off value that is not in the defaults, use arbitrary values
5
5
  > **INFO**
6
6
  > To generate an arbitrary style, use parentheses notation with almost any default utility class.
7
7
  >
8
- > Square bracket notation (`[10px]`) is **not supported** because Titanium handles platform and conditional statements in `.tss` files differently. Use parentheses (`(10px)`) instead.
8
+ > Square bracket notation (`[10]`) is **not supported** because Titanium already uses square brackets for platform and conditional statements in `.tss` files. Use parentheses (`(10)`) instead.
9
9
 
10
10
  <!-- TOC-START -->
11
11
  ## Contents
@@ -26,19 +26,25 @@ Starting with PurgeTSS v7.8.0, the build runs a pre-validation pass over class n
26
26
 
27
27
  Each error block includes:
28
28
 
29
- - The file path where the offending class lives
30
- - The line number inside that file
31
- - The offending class name (as authored)
32
- - A `Fix:` suggestion with the corrected class name
29
+ - `Class:` the offending class name (as authored)
30
+ - `File:` the first file where it appears (plus a count of other locations, if any)
31
+ - `Line:` the line number inside that file
32
+ - `Content:` the full content of that line
33
+ - `Issue:` what is wrong
34
+ - `Fix:` a suggestion with the corrected class name
33
35
 
34
- Example shape:
36
+ Example output:
35
37
 
36
38
  ```
37
- Class Syntax Error
38
- File: app/views/index.xml
39
- Line: 12
40
- Found: top-(-10)
41
- Fix: -top-(10)
39
+ ::PurgeTSS:: Class Syntax Error
40
+ Class: "top-(-10)"
41
+ File: "app/views/index.xml"
42
+ Line: 12
43
+ Content: <View class="top-(-10)" />
44
+
45
+ Issue: Negative sign is inside the parentheses
46
+ Fix: Use "-top-(10)" — PurgeTSS expects the "-" prefix BEFORE the rule, not inside the value
47
+ ::PurgeTSS:: Found 1 class syntax error — fix the class above and re-run purgetss
42
48
  ```
43
49
 
44
50
  ### Detected patterns
@@ -48,7 +54,7 @@ The validator catches five narrow, actionable mistakes:
48
54
  | Pattern | Offending input | Suggested fix | Notes |
49
55
  | ----------------------------- | --------------- | ------------- | ---------------------------------------------------------------- |
50
56
  | Inverted negative sign | `top-(-10)` | `-top-(10)` | The `-` prefix goes before the rule, not inside the value |
51
- | Square-bracket notation | `top-[10px]` | `top-(10px)` | PurgeTSS uses parentheses, not square brackets, for arbitrary values (v7.10.1 reworded the error from `Tailwind-style brackets "[ ]"` to `Square brackets "[ ]" are not supported`) |
57
+ | Square-bracket notation | `top-[10]` | `top-(10)` | PurgeTSS uses parentheses, not square brackets, for arbitrary values (v7.10.1 reworded the error from `Tailwind-style brackets "[ ]"` to `Square brackets "[ ]" are not supported`) |
52
58
  | Empty parentheses | `wh-()` | (flagged, no auto-fix) | Add a value such as `wh-(10)` |
53
59
  | Whitespace inside parentheses | `wh-( 200 )` | `wh-(200)` | No spaces allowed between `(`, the value, and `)` |
54
60
  | Redundant `px` unit | `top-(10px)` | `top-(10)` | PurgeTSS treats unit-less arbitrary values as pixels |
@@ -57,9 +63,11 @@ The validator catches five narrow, actionable mistakes:
57
63
 
58
64
  The pre-validator only fires on the five patterns above. Any other unknown class — typos, custom utilities not yet declared, vendor classes that are not enabled in `config.cjs` — is **not** reported as a `Class Syntax Error`. Those classes continue to flow into the `// Unused or unsupported classes` comment block in `app.tss`, exactly like before. This keeps the validator focused on actionable mistakes and avoids noise while you sketch out class names.
59
65
 
66
+ > **Units: write arbitrary values unitless.** Because of the redundant-`px` rule, any class like `m-(50px)`, `w-(2300px)` or `text-(48px)` halts the purge. Write `m-(50)`, `w-(2300)`, `text-(48)` instead; other units such as `rem`, `in` or `%` are not flagged.
67
+
60
68
  ### v7.8.0 parser fix for negatives inside parentheses
61
69
 
62
- Before v7.8.0, an inverted negative such as `top-(-10)` could be silently misparsed by the arbitrary-value pipeline (the `-` inside the parentheses confused the token matcher, producing wrong or missing TSS output without any warning). v7.8.0 hardens that path: the parser now correctly recognizes `top-(-10)` as authored, classifies it as an inverted-negative-sign error, and surfaces the `Class Syntax Error` block with the `-top-(10)` fix instead of producing silent garbage.
70
+ Before v7.8.0, a hyphen inside the value (e.g. `top-(-10)`, `mt-(-5)`, `rotate-(-45)`, `origin-(-10,-20)`) crashed the arbitrary-value parser with an unhandled `Cannot read properties of null (reading 'pop')` exception. v7.8.0 rewrote the parser to extract the `(...)` part first, so the value can contain any characters, and classes with empty values (`wh-()`) or unbalanced parentheses (`wh-(`) now produce a "not yet supported" comment instead of invalid TSS. `top-(-10)` itself is now caught by the pre-validator as an inverted negative sign, with the `-top-(10)` fix.
63
71
 
64
72
  ## Arbitrary nesting depth in `theme` objects (v7.10.0)
65
73
 
@@ -168,23 +176,32 @@ You can set an arbitrary value for any of the following color properties:
168
176
  - `active-tint-` (*hex-rgb-or-rgba-value*)
169
177
  - `active-title-` (*hex-rgb-or-rgba-value*)
170
178
  - `badge-` (*hex-rgb-or-rgba-value*)
179
+ - `badge-bg-` (*hex-rgb-or-rgba-value*)
180
+ - `badge-text-` (*hex-rgb-or-rgba-value*)
171
181
  - `bar-` (*hex-rgb-or-rgba-value*)
172
182
  - `bg-` (*hex-rgb-or-rgba-value*)
183
+ - `bg-disabled-` (*hex-rgb-or-rgba-value*)
173
184
  - `bg-focused-` (*hex-rgb-or-rgba-value*)
174
185
  - `bg-selected-` (*hex-rgb-or-rgba-value*)
175
186
  - `bg-selected-from-` (*hex-rgb-or-rgba-value*)
176
187
  - `bg-selected-to-` (*hex-rgb-or-rgba-value*)
177
188
  - `border-` (*hex-rgb-or-rgba-value*)
189
+ - `colors-` (*hex-rgb-or-rgba-value*)
190
+ - `content-scrim-` (*hex-rgb-or-rgba-value*)
178
191
  - `current-page-indicator-` (*hex-rgb-or-rgba-value*)
179
192
  - `date-time-` (*hex-rgb-or-rgba-value*)
180
193
  - `disabled-` (*hex-rgb-or-rgba-value*)
181
194
  - `drop-shadow-` (*hex-rgb-or-rgba-value*)
182
195
  - `from-` (*hex-rgb-or-rgba-value*)
183
196
  - `highlighted-` (*hex-rgb-or-rgba-value*)
197
+ - `hint-text-` (*hex-rgb-or-rgba-value*)
198
+ - `icon-` (*hex-rgb-or-rgba-value*)
184
199
  - `image-touch-feedback-` (*hex-rgb-or-rgba-value*)
185
200
  - `indicator-` (*hex-rgb-or-rgba-value*)
186
201
  - `keyboard-toolbar-` (*hex-rgb-or-rgba-value*)
202
+ - `light-` (*hex-rgb-or-rgba-value*)
187
203
  - `nav-tint-` (*hex-rgb-or-rgba-value*)
204
+ - `navigation-icon-` (*hex-rgb-or-rgba-value*)
188
205
  - `on-tint-` (*hex-rgb-or-rgba-value*)
189
206
  - `page-indicator-` (*hex-rgb-or-rgba-value*)
190
207
  - `paging-control-` (*hex-rgb-or-rgba-value*)
@@ -193,17 +210,21 @@ You can set an arbitrary value for any of the following color properties:
193
210
  - `results-bg-` (*hex-rgb-or-rgba-value*)
194
211
  - `results-separator-` (*hex-rgb-or-rgba-value*)
195
212
  - `selected-` (*hex-rgb-or-rgba-value*)
213
+ - `selected-bg-` (*hex-rgb-or-rgba-value*)
196
214
  - `selected-button-` (*hex-rgb-or-rgba-value*)
197
215
  - `selected-subtitle-` (*hex-rgb-or-rgba-value*)
198
216
  - `selected-text-` (*hex-rgb-or-rgba-value*)
199
217
  - `separator-` (*hex-rgb-or-rgba-value*)
200
218
  - `shadow-` (*hex-rgb-or-rgba-value*)
219
+ - `status-bar-bg-` (*hex-rgb-or-rgba-value*)
201
220
  - `subtitle-` (*hex-rgb-or-rgba-value*)
221
+ - `subtitle-text-` (*hex-rgb-or-rgba-value*)
202
222
  - `tabs-bg-` (*hex-rgb-or-rgba-value*)
203
223
  - `tabs-bg-selected-` (*hex-rgb-or-rgba-value*)
204
224
  - `text-` (*hex-rgb-or-rgba-value*)
205
225
  - `thumb-tint-` (*hex-rgb-or-rgba-value*)
206
226
  - `tint-` (*hex-rgb-or-rgba-value*)
227
+ - `tint-color-` (*hex-rgb-or-rgba-value*)
207
228
  - `title-` (*hex-rgb-or-rgba-value*)
208
229
  - `title-attributes-` (*hex-rgb-or-rgba-value*)
209
230
  - `title-attributes-shadow-` (*hex-rgb-or-rgba-value*)
@@ -211,10 +232,11 @@ You can set an arbitrary value for any of the following color properties:
211
232
  - `to-` (*hex-rgb-or-rgba-value*)
212
233
  - `touch-feedback-` (*hex-rgb-or-rgba-value*)
213
234
  - `track-tint-` (*hex-rgb-or-rgba-value*)
235
+ - `view-shadow-` (*hex-rgb-or-rgba-value*)
214
236
 
215
237
  ## Spacing Properties
216
238
 
217
- You can set arbitrary values for most size and dimension properties using `rem`, `px`, or `pt` values, directly in XML files or in `config.cjs`.
239
+ You can set arbitrary values for most size and dimension properties using unitless numbers or units such as `rem`, `in`, or `%`, directly in XML files or in `config.cjs`. A `px` suffix is rejected by the pre-validator (see above).
218
240
 
219
241
  ```xml
220
242
  <Alloy>
@@ -274,6 +296,7 @@ You can set arbitrary values for most size and dimension properties using `rem`,
274
296
  - Width and height
275
297
  - `h-` (*any-size-value-and-unit*)
276
298
  - `w-` (*any-size-value-and-unit*)
299
+ - `wh-` (*any-size-value-and-unit*)
277
300
  - `indentation-level-` (*any-size-value-and-unit*)
278
301
  - `keyboard-toolbar-h-` (*any-size-value-and-unit*)
279
302
  - Left and right button padding
@@ -327,10 +350,15 @@ You can set arbitrary values for most size and dimension properties using `rem`,
327
350
  - `rounded-t-` (*any-size-value-and-unit*)
328
351
  - `rounded-tl-` (*any-size-value-and-unit*)
329
352
  - `rounded-tr-` (*any-size-value-and-unit*)
353
+ - `rounded-corners-` (*four-comma-separated-values*), e.g. `rounded-corners-(1,2,3,4)` → `borderRadius: [1, 2, 3, 4]`
354
+ - `rounded-full-` (*any-size-value-and-unit*), e.g. `rounded-full-(40)` → `width: 40, height: 40, borderRadius: 20`
330
355
  - `row-h-` (*any-size-value-and-unit*)
331
356
  - `section-header-top-padding-` (*any-size-value-and-unit*)
332
357
  - `separator-h-` (*any-size-value-and-unit*)
333
358
  - `shadow-radius-` (*any-size-value-and-unit*)
359
+ - Target image height and width
360
+ - `target-image-h-` (*any-size-value-and-unit*)
361
+ - `target-image-w-` (*any-size-value-and-unit*)
334
362
  - xOffset and yOffset
335
363
  - `x-offset-` (*any-size-value-and-unit*)
336
364
  - `y-offset-` (*any-size-value-and-unit*)
@@ -344,36 +372,44 @@ You can set arbitrary values for the following properties, like border radius an
344
372
  - `active-tab-` (*number-value*)
345
373
  - `border-width-` (*number-value*)
346
374
  - `cache-size-` (*number-value*)
375
+ - `cancel-` (*number-value*)
347
376
  - `count-down-` (*in-milliseconds*)
348
377
  - `delay-` (*in-milliseconds*)
378
+ - `destructive-` (*number-value*)
349
379
  - `duration-` (*in-milliseconds*)
350
380
  - `elevation-` (*number-value*)
351
381
  - `font-` (*valid-font-weight-value*)
352
382
  - `horizontal-margin-` (*decimal-value*)
383
+ - `index-` (*number-value*)
353
384
  - `lines-` (*number-value*)
385
+ - `max-` (*number-value*)
354
386
  - `max-length-` (*number-value*)
355
387
  - `max-lines-` (*number-value*)
356
388
  - `max-zoom-scale-` (*decimal-value*)
389
+ - `maximum-` (*number-value*)
357
390
  - `min-zoom-scale-` (*decimal-value*)
358
391
  - `min-` (*number-value*)
392
+ - `minimum-` (*number-value*)
359
393
  - `minimum-text-` (*number-value*)
360
394
  - `opacity-` (*decimal-value-from-0-to-1*)
361
395
  - `origin-` (*x-and-y-coordinates*)
362
396
  - `paging-control-alpha-` (*decimal-value-from-0-to-1*)
363
397
  - `paging-control-timeout-` (*in-milliseconds*)
398
+ - `preferred-` (*number-value*)
364
399
  - `repeat-count-` (*number-value*)
365
400
  - `repeat-` (*number-value*)
366
401
  - `rotate-` (*number-value*)
367
402
  - `scale-` (*decimal-value*)
368
403
  - `text-size-` (*number-value*)
369
404
  - `timeout-` (*in-milliseconds*)
405
+ - `value-` (*number-value*)
370
406
  - `vertical-margin-` (*decimal-value*)
371
407
  - `z-` (*number-value*)
372
408
  - `zoom-scale-` (*decimal-value*)
373
409
 
374
410
  ## Examples
375
411
 
376
- You can use any supported units depending on the property you are generating. Use `hex` or `rgba` values for any color property, or `rem` or `px` for position and sizing properties.
412
+ You can use any supported units depending on the property you are generating. Use `hex` or `rgba` values for any color property, or unitless numbers or `rem` for position and sizing properties.
377
413
 
378
414
  ### Credit Card
379
415
 
@@ -381,7 +417,7 @@ You can use any supported units depending on the property you are generating. Us
381
417
  <Alloy>
382
418
  <Window class="bg-(#53606b)">
383
419
  <View class="w-(2in) h-(3.5in) bg-(#4C61E4) rounded-(20)">
384
- <View class="m-(50px) h-screen w-screen">
420
+ <View class="m-(50) h-screen w-screen">
385
421
  <View class="horizontal ml-0 mt-0">
386
422
  <View class="w-(3rem) h-(3rem) rounded-(1.5rem) opacity-(0.35) bg-white" />
387
423
  <View class="-ml-(20) w-(3rem) h-(3rem) rounded-(1.5rem) opacity-(0.25) bg-white" />
@@ -407,10 +443,10 @@ Try this example on an iPad or tablet.
407
443
  ```xml
408
444
  <Alloy>
409
445
  <Window class="bg-white">
410
- <View class="bg-(#1B6F3F) w-(2300px) h-(1600px) ml-0 mt-0">
411
- <View class="mt-(100px) h-(200px) left-(150px) horizontal">
412
- <Label class="fas fa-cloud text-(48px) text-white" />
413
- <Label class="ml-(30px) text-(48px) h-(200px) font-bold text-white">ZULTYS</Label>
446
+ <View class="bg-(#1B6F3F) w-(2300) h-(1600) ml-0 mt-0">
447
+ <View class="mt-(100) h-(200) left-(150) horizontal">
448
+ <Label class="fas fa-cloud text-(48) text-white" />
449
+ <Label class="ml-(30) text-(48) h-(200) font-bold text-white">ZULTYS</Label>
414
450
  </View>
415
451
  </View>
416
452
  </Window>
@@ -422,9 +458,9 @@ Try this example on an iPad or tablet.
422
458
  ```xml
423
459
  <Alloy>
424
460
  <Window class="bg-(#53606b)" backgroundImage="https://i.pinimg.com/originals/ab/70/a1/ab70a19f087cc9ba2b03e3bee71acc3e.jpg">
425
- <View class="h-(150px) bg-(#53A500) horizontal mt-0 w-screen">
426
- <Label class="w-(4.5rem) text-(16px) h-(150px) text-(#12681E) text-center font-bold" text="Home" />
427
- <Label class="w-(4.5rem) text-(16px) h-(150px) text-(#12681E) bg-(#6DB400) text-center font-bold" text="News" />
461
+ <View class="h-(150) bg-(#53A500) horizontal mt-0 w-screen">
462
+ <Label class="w-(4.5rem) text-(16) h-(150) text-(#12681E) text-center font-bold" text="Home" />
463
+ <Label class="w-(4.5rem) text-(16) h-(150) text-(#12681E) bg-(#6DB400) text-center font-bold" text="News" />
428
464
  </View>
429
465
  </Window>
430
466
  </Alloy>
@@ -0,0 +1,105 @@
1
+ # Brand Padding & Geometry
2
+
3
+ How `purgetss brand` sizes the logo inside each piece: per-piece padding, how the source is read and how sharp the output is, the shared splash rule, rounded non-icon artwork, and the Android adaptive mask math.
4
+
5
+ For the `brand` command itself, its pieces and config, see [App Icons & Branding](./app-branding.md).
6
+
7
+ ## Padding guidance
8
+
9
+ Padding belongs to the piece, not to the project. Each piece has its own key and its own flag:
10
+
11
+ | Piece | Config | Flag | Default |
12
+ | --- | --- | --- | --- |
13
+ | `adaptive` | `brand.adaptive.padding` | `--android-adaptive-padding` | `18%` |
14
+ | `legacy-icon` | `brand.legacyIcon.padding` | `--android-legacy-padding` | `10%` |
15
+ | `appicon` | `brand.appicon.padding` | `--appicon-padding` | `10%` |
16
+ | `icon` | `brand.icon.padding` | `--ios-padding` | `0%` |
17
+ | `feature-graphic` | `brand.featureGraphic.padding` | `--feature-graphic-padding` | `12%` |
18
+ | `launch-logo` | `brand.launchLogo.padding` | `--launch-logo-padding` | `12%` |
19
+ | `android-splash` | `brand.androidSplash.padding` | `--android-splash-padding` | `26%` |
20
+ | `ios-splash` | `brand.iosSplash.padding` | `--ios-splash-padding` | `26%` |
21
+
22
+ `--padding` is a shortcut for the two Android launcher paddings in a single run, and `--splash-padding` for the two splash paddings. `--ios-padding` moves the four square iOS/marketplace pieces together (`icon`, `dark`, `tinted`, `marketplace`); in config each of them has its own key.
23
+
24
+ There is deliberately **no global padding value that cascades down**. The defaults answer to different constraints: `18%` answers to the Android launcher mask, while finished square iOS/store artwork stays full-bleed at `0%`. One inherited number could silently break the launcher mask or add an unwanted frame to finished artwork. `background`, which has no such trap, is inherited from `brand.background`.
25
+
26
+ ### How the source is read, and how sharp the output is
27
+
28
+ Two things are worth knowing about what happens to your `logo.svg` or `logo.png` before any padding is applied.
29
+
30
+ **The container is what counts, not the artwork's bounding box.** An SVG is read at its `viewBox`, a raster at its full canvas, and neither is trimmed to where the pixels actually are. So whatever margin a designer baked into the file **adds** to the padding configured per piece. A round logo exported inside a 2048×2048 PNG with 25% of its own air, generated at `adaptive: { padding: '18%' }`, ends up covering about 32% of the icon canvas, not 64%. If a mark comes out smaller than the numbers suggest, that is almost always why: crop the source or lower the padding.
31
+
32
+ **The masters are sized to the run.** The source is rasterized once into two intermediate masters, and every piece scales down from them, so their resolution is the ceiling on output sharpness. Rather than a fixed size, `brand` measures the largest number of pixels any selected piece will ask for and builds the masters at exactly that. A default run reports it:
33
+
34
+ ```text
35
+ • Masters at 1024 px — the largest any selected piece asks for
36
+ ```
37
+
38
+ Lower a padding and the figure rises with it (`--splash-padding 4` needs 1413 px), so output never goes soft against a fixed ceiling. Every destination is a reduction or the same size, never an upscale.
39
+
40
+ The one case this cannot fix is a raster source that is simply too small: a 512-px PNG cannot produce a sharp 1024-px icon. Prefer SVG, or a PNG of at least 1024×1024.
41
+
42
+ ### Splash padding
43
+
44
+ The 28 splash images (`default.png`, the 11 `res-*`, and the 16 iPhone launch images) share one rule: the logo is fitted into a square whose side is a share of the canvas's **shorter** side.
45
+
46
+ Measuring against the shorter side is what lets a single number work across canvases as different as 1440×2560 and 800×480: at 800×480 the limit comes from the height, at 240×400 from the width, and the logo keeps the same visual weight in portrait and in landscape.
47
+
48
+ | `androidSplash.padding` / `iosSplash.padding` | Logo | `default.png` (1440×2560) | `res-notlong-port-mdpi` (320×480) |
49
+ | --- | --- | --- | --- |
50
+ | `20%` | 60% of the shorter side | 864 px | 192 px |
51
+ | `26%` (default) | 48% | 691 px | 153 px |
52
+ | `30%` | 40% | 576 px | 128 px |
53
+ | `35%` | 30% | 432 px | 96 px |
54
+
55
+ The `26%` default is calibrated against the Titanium template itself: the Alloy logo in the stock `default.png` measures 665×488 px on a 1440×2560 canvas, so `26%` lands within 4% of the size Titanium ships.
56
+
57
+ Before v7.13.0 none of this was configurable: `default.png` used a hardcoded box of 72% × 26% of its own canvas, and the `res-*` set a separate hardcoded 60%. Two rules for the same piece, neither adjustable.
58
+
59
+ ## Rounded non-icon artwork
60
+
61
+ `brand.artworkCornerRadius` rounds artwork only in the 16 iPhone launch images, Android `default.png` plus its 11 qualifier variants, `MarketplaceArtworkFeature.png`, and `LaunchLogo.png`. A piece may override it with `cornerRadius`; `brand.splashCornerRadius` is an optional shared override for the two legacy splash pieces.
62
+
63
+ ```javascript
64
+ brand: {
65
+ artworkCornerRadius: '22%',
66
+ iosSplash: { padding: '26%', cornerRadius: '18%' },
67
+ featureGraphic: { padding: '12%', cornerRadius: '20%' }
68
+ }
69
+ ```
70
+
71
+ Values are integer numbers or percentage strings from `0` through `50`, measured against the shorter side of the resized artwork. `0%` preserves the previous output byte for byte; `50%` makes square artwork circular and a wordmark capsule-shaped. Normal and `--dry-run` summaries report the effective padding and radius.
72
+
73
+ Precedence is:
74
+
75
+ - Feature Graphic / LaunchLogo: piece-specific flag → `--artwork-corner-radius` → piece config → `brand.artworkCornerRadius` → `0%`.
76
+ - Legacy splashes: platform flag → `--splash-corner-radius` → `--artwork-corner-radius` → piece config → `brand.splashCornerRadius` → `brand.artworkCornerRadius` → `0%`.
77
+
78
+ Store and launcher icons remain unmasked for platform processing. Despite their filenames, `iTunesConnect.png` and `MarketplaceArtwork.png` are app icons: Apple asks for square, unmasked artwork and Google Play applies its own rounded mask, so pre-rounding them would be wrong. `cornerRadius` is rejected in `DefaultIcon*`, `iTunesConnect.png`, `MarketplaceArtwork.png`, adaptive, legacy, and app icons, Android 12+ `splash_icon.png`, notification icons, and any other unsupported piece; invalid, fractional, negative, non-numeric, or greater-than-50 values also abort before files are written.
79
+
80
+ ### Adaptive icon padding
81
+
82
+ Android's adaptive canvas is 108 dp. The mask leaves roughly 72 dp visible, and the **guaranteed** safe area is a 66 dp circle inscribed in it. What each padding means in those terms:
83
+
84
+ | Padding | Logo | vs. the 66 dp safe circle | vs. the ~72 dp the mask shows |
85
+ | --- | --- | --- | --- |
86
+ | `15%` | 75.6 dp | outside | **outside** — clipped on any launcher |
87
+ | `16%` | 73.4 dp | outside | **outside** |
88
+ | `18%` | 69.1 dp | corners outside | inside — **the default** |
89
+ | `19.44%` | 66.0 dp | exactly on it | inside |
90
+ | `20%` | 64.8 dp | inside | inside — most conservative |
91
+
92
+ `18%` sits between the guaranteed circle and the mask edge: a logo that carries its own margin never reaches those corners, which is why it is a safe default in practice. Raise it to `20%` if your mark runs edge to edge and you see clipping on a circular launcher.
93
+
94
+ 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.
95
+
96
+ The official Android spec floor is `19.44%` (108dp canvas, 66dp inscribed safe-zone circle). That is the theoretical worst-case for aggressive adaptive masks, which is why the adaptive default sits close to it.
97
+
98
+ ### Legacy icon padding
99
+
100
+ Legacy `ic_launcher.png` does not go through the same adaptive mask, so it can usually run tighter. That is why the default for `brand.legacyIcon.padding` is `10%`.
101
+
102
+ ## See also
103
+
104
+ - [App Icons & Branding](./app-branding.md) — the `brand` command, its pieces and config.
105
+ - [`brand` command reference](./cli-commands.md#brand-command) — terse flag list, including every padding and corner-radius flag.