@maccesar/titools 5.0.0 → 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.
- package/README.md +26 -3
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +27 -24
- package/skills/purgetss/references/EXAMPLES.md +52 -41
- package/skills/purgetss/references/adopting-purgetss.md +137 -0
- package/skills/purgetss/references/animation-advanced.md +18 -5
- package/skills/purgetss/references/animation-system.md +21 -9
- package/skills/purgetss/references/app-branding.md +36 -97
- package/skills/purgetss/references/appearance-module.md +70 -5
- package/skills/purgetss/references/apply-directive.md +14 -6
- package/skills/purgetss/references/arbitrary-values.md +59 -23
- package/skills/purgetss/references/brand-padding-geometry.md +105 -0
- package/skills/purgetss/references/class-categories.md +684 -272
- package/skills/purgetss/references/class-index-properties.md +713 -607
- package/skills/purgetss/references/class-index.md +149 -84
- package/skills/purgetss/references/classic-projects.md +3 -3
- package/skills/purgetss/references/cli-commands.md +27 -213
- package/skills/purgetss/references/color-commands.md +218 -0
- package/skills/purgetss/references/configurable-properties.md +17 -2
- package/skills/purgetss/references/custom-fonts.md +21 -16
- package/skills/purgetss/references/custom-rules.md +7 -7
- package/skills/purgetss/references/customization-deep-dive.md +14 -11
- package/skills/purgetss/references/dynamic-component-creation.md +104 -61
- package/skills/purgetss/references/grid-layout.md +21 -24
- package/skills/purgetss/references/icon-fonts.md +37 -20
- package/skills/purgetss/references/installation-setup.md +1 -1
- package/skills/purgetss/references/ios-large-titles.md +12 -11
- package/skills/purgetss/references/migration-guide.md +101 -10
- package/skills/purgetss/references/multi-density-images.md +44 -24
- package/skills/purgetss/references/opacity-modifier.md +1 -1
- package/skills/purgetss/references/performance-tips.md +5 -4
- package/skills/purgetss/references/platform-modifiers.md +2 -2
- package/skills/purgetss/references/purgetss-ui-classic.md +21 -5
- package/skills/purgetss/references/semantic-colors.md +20 -13
- package/skills/purgetss/references/smart-mappings.md +8 -7
- package/skills/purgetss/references/svg-pipeline.md +21 -13
- package/skills/purgetss/references/tikit-components.md +2 -2
- package/skills/purgetss/references/titanium-resets.md +27 -17
- package/skills/purgetss/references/ui-ux-design.md +38 -26
- package/skills/purgetss/references/values-and-units.md +1 -2
- package/skills/purgetss/references/version-history.md +32 -4
|
@@ -84,7 +84,7 @@ The `play`, `toggle`, `open`, `close`, `apply`, and `sequence` methods accept an
|
|
|
84
84
|
| --- | --- |
|
|
85
85
|
| `open:` | Properties applied during open state |
|
|
86
86
|
| `close:` | Properties applied during close state |
|
|
87
|
-
| `complete:` | Additional properties applied after the active `play`/`toggle`/`open`/`close` state, or immediately after `apply` |
|
|
87
|
+
| `complete:` | Additional properties applied after the active `play`/`toggle`/`open`/`close` state (and after each view of a `sequence`), or immediately after `apply` |
|
|
88
88
|
| `children:` | Global properties for all children of a View |
|
|
89
89
|
| `child:` | Individual properties for specific children |
|
|
90
90
|
| `bounds:` | Drag boundaries within parent |
|
|
@@ -93,7 +93,9 @@ The `play`, `toggle`, `open`, `close`, `apply`, and `sequence` methods accept an
|
|
|
93
93
|
|
|
94
94
|
### Timing and Special Classes
|
|
95
95
|
|
|
96
|
-
`delay-*`, `duration-*`, `rotate-*`, `scale-*`, `repeat-*`, `zoom-in-*`, `zoom-out-*`, `drag-apply`, `drag-animate`, `ease-in`, `ease-out`, `ease-linear`, `ease-in-out`, `vertical-constraint`, `horizontal-constraint`.
|
|
96
|
+
`delay-*`, `duration-*`, `rotate-*`, `scale-*`, `repeat-*`, `zoom-in-*`, `zoom-out-*`, `drag-apply`, `drag-animate`, `ease-in`, `ease-out`, `ease-linear`, `ease-in-out`, `vertical-constraint`, `horizontal-constraint`, `debug`.
|
|
97
|
+
|
|
98
|
+
The `debug` class sets `debug: true` (in Classic, pass `debug: true` to `createAnimation()`). The object then logs every method call and internal step with `console.warn('::ti.animation:: …')`.
|
|
97
99
|
|
|
98
100
|
---
|
|
99
101
|
|
|
@@ -101,6 +103,8 @@ The `play`, `toggle`, `open`, `close`, `apply`, and `sequence` methods accept an
|
|
|
101
103
|
|
|
102
104
|
Runs the animation for a single view or an array of views. Toggles between open and close states on repeated calls.
|
|
103
105
|
|
|
106
|
+
`play` and `toggle` do nothing while the object's internal `playing` flag is set. The flag turns on when a native animation starts and off at the first completion event, so with an array it clears when the first view finishes. `open`, `close`, `apply`, and `sequence` do not check it.
|
|
107
|
+
|
|
104
108
|
```javascript
|
|
105
109
|
$.myAnimation.play($.myView)
|
|
106
110
|
```
|
|
@@ -262,6 +266,10 @@ function showPanel() {
|
|
|
262
266
|
function closePanel() {
|
|
263
267
|
$.panelAnim.close($.panel, () => { $.panel.hide() })
|
|
264
268
|
}
|
|
269
|
+
|
|
270
|
+
function onOverlayTap({ source }) {
|
|
271
|
+
if (source.id === 'panel') closePanel()
|
|
272
|
+
}
|
|
265
273
|
```
|
|
266
274
|
|
|
267
275
|
Key classes: `zoom-in-110` (pop effect), `opacity-to-100` (fade overlay), `close:duration-0 open:duration-100` (instant close, animated open).
|
|
@@ -341,6 +349,8 @@ function onClose() {
|
|
|
341
349
|
|
|
342
350
|
After calling `draggable()`, enable collision detection based on center-point hit testing.
|
|
343
351
|
|
|
352
|
+
Include the dragged views themselves in the `views` list: drop callbacks, `snap-center`, and `snap-back` run only for a view registered with `detectCollisions()`.
|
|
353
|
+
|
|
344
354
|
```javascript
|
|
345
355
|
$.myAnimation.draggable(views)
|
|
346
356
|
$.myAnimation.detectCollisions(views, dragCB, dropCB)
|
|
@@ -351,9 +361,11 @@ $.myAnimation.detectCollisions(views, dragCB, dropCB)
|
|
|
351
361
|
| Parameter | Type | Description |
|
|
352
362
|
| --- | --- | --- |
|
|
353
363
|
| `views` | `View/Array` | Views to register for collision detection |
|
|
354
|
-
| `dragCB` | `Function(source, target)` | Called during drag; `target` is the view under the drag center, or `null` when leaving |
|
|
364
|
+
| `dragCB` | `Function(source, target)` | Called during drag; `target` is the view under the drag center, or `null` when leaving. Also called with `null` whenever a registered view is released |
|
|
355
365
|
| `dropCB` | `Function(source, target)` | Called on drop when a collision target is found |
|
|
356
366
|
|
|
367
|
+
Hit testing during movement runs only when a `dragCB` was supplied. Without one, the drop uses only the final hit test at release; the last-hovered target is not available as a fallback.
|
|
368
|
+
|
|
357
369
|
### Snap Classes
|
|
358
370
|
|
|
359
371
|
Off by default -- opt-in via classes on the `<Animation>` object:
|
|
@@ -436,12 +448,12 @@ $.myAnimation.sequence(views, callback)
|
|
|
436
448
|
```
|
|
437
449
|
|
|
438
450
|
```javascript
|
|
439
|
-
$.
|
|
451
|
+
$.revealAnim.sequence([$.title, $.subtitle, $.cta], () => {
|
|
440
452
|
console.log('All views animated in sequence')
|
|
441
453
|
})
|
|
442
454
|
|
|
443
455
|
// Use close() to force state back before revealing again
|
|
444
|
-
$.revealAnim.close(
|
|
456
|
+
$.revealAnim.close([$.title, $.subtitle, $.cta])
|
|
445
457
|
```
|
|
446
458
|
|
|
447
459
|
---
|
|
@@ -459,7 +471,7 @@ $.myAnimation.swap(view1, view2)
|
|
|
459
471
|
- Resets the source transform while persisting the exchanged positions
|
|
460
472
|
- Temporarily elevates z-index; then assigns z-index from the current draggable-registry order rather than restoring arbitrary original values
|
|
461
473
|
- Updates `_originLeft`/`_originTop` for subsequent drag operations
|
|
462
|
-
- **
|
|
474
|
+
- **Position resolution**: `view2`'s destination is read from `view2.rect`, but `view1`'s home comes from its stored drag origin or its `top`/`left`, with no `rect` fallback. Give `view1` explicit `top`/`left` unless it has already been dragged or moved by a helper
|
|
463
475
|
- **Bounce-back safe**: completes any in-progress bounce-back before swapping
|
|
464
476
|
|
|
465
477
|
### Memory Card Game Example
|
|
@@ -469,7 +481,7 @@ let firstCard = null
|
|
|
469
481
|
function onCardTap({ source }) {
|
|
470
482
|
$.flipAnim.open(source)
|
|
471
483
|
if (!firstCard) { firstCard = source; return }
|
|
472
|
-
if (firstCard.
|
|
484
|
+
if (firstCard.value === source.value) {
|
|
473
485
|
firstCard = null
|
|
474
486
|
} else {
|
|
475
487
|
$.gameAnim.swap(firstCard, source)
|
|
@@ -552,7 +564,7 @@ $.puzzleAnim.draggable(pieces)
|
|
|
552
564
|
$.puzzleAnim.detectCollisions(pieces.concat(slots),
|
|
553
565
|
null,
|
|
554
566
|
(source, target) => {
|
|
555
|
-
if (source.
|
|
567
|
+
if (source.value === target.value) {
|
|
556
568
|
$.puzzleAnim.undraggable(source)
|
|
557
569
|
source.applyProperties({ opacity: 0.6 })
|
|
558
570
|
}
|
|
@@ -643,7 +655,7 @@ $.anim.transition(screensB, fanOut)
|
|
|
643
655
|
|
|
644
656
|
```javascript
|
|
645
657
|
const photos = [$.photo1, $.photo2, $.photo3]
|
|
646
|
-
function doFan() { $.galleryAnim.transition(photos,
|
|
658
|
+
function doFan() { $.galleryAnim.transition(photos, fanOut) }
|
|
647
659
|
function doStack() { $.galleryAnim.transition(photos, stack) }
|
|
648
660
|
$.galleryAnim.draggable(photos) // photos keep rotation/scale while dragging
|
|
649
661
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# App Icons & Branding
|
|
2
2
|
|
|
3
|
-
The `purgetss brand` command generates the complete Titanium branding set from one main SVG or PNG logo: launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, and both splash sets. Per-piece overrides are available when needed. Alloy and Classic layouts are auto-detected, and since v7.14.0 a normal run follows the platforms enabled in `tiapp.xml`.
|
|
3
|
+
The `purgetss brand` command generates the complete Titanium branding set from one main SVG or PNG logo: launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, and both splash sets. Per-piece overrides are available when needed. Alloy and Classic layouts are auto-detected, and since v7.14.0 a normal run follows the platforms enabled in `tiapp.xml`. The rule: if an enabled platform can consume the file, `brand` generates or updates it.
|
|
4
4
|
|
|
5
|
-
The command also creates Titanium-consumed Classic paths that a fresh Classic template does not seed, including the 11 Android `Resources/android/images/res-*` splash variants. An explicit `--only` is an intentional override that can prepare assets for a disabled deployment target.
|
|
5
|
+
The command also creates Titanium-consumed Classic paths that a fresh Classic template does not seed, including the 11 Android `Resources/android/images/res-*` splash variants. An explicit `--only` is an intentional override that can prepare assets for a disabled deployment target. A normal run names the pieces it left out (`Skipped for disabled deployment target(s): …`), and stops with a warning when no selected piece matches an enabled target.
|
|
6
6
|
|
|
7
7
|
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).
|
|
8
8
|
|
|
@@ -30,8 +30,7 @@ For the terse flag reference, see the [`brand` command reference](./cli-commands
|
|
|
30
30
|
- [iPhone launch images](#iphone-launch-images)
|
|
31
31
|
- [iOS 18+ Dark and Tinted variants](#ios-18-dark-and-tinted-variants)
|
|
32
32
|
- [Brand color](#brand-color)
|
|
33
|
-
- [Padding
|
|
34
|
-
- [Rounded non-icon artwork](#rounded-non-icon-artwork)
|
|
33
|
+
- [Padding and geometry](#padding-and-geometry)
|
|
35
34
|
- [Cleanup legacy branding artifacts](#cleanup-legacy-branding-artifacts)
|
|
36
35
|
- [Troubleshooting](#troubleshooting)
|
|
37
36
|
- [Flag reference](#flag-reference)
|
|
@@ -64,7 +63,7 @@ Pass `--dry-run` to preview without writing any files:
|
|
|
64
63
|
purgetss brand --dry-run
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
You may also pass a source directly. When
|
|
66
|
+
You may also pass a source directly. When the project (Alloy or Classic) has no canonical `purgetss/brand/logo.{svg,png}`, the confirmed in-place run **moves** that positional `.svg`/`.png` to `purgetss/brand/logo.<ext>` and reports the destination; `--dry-run` only reports where it would go. An existing canonical logo is never replaced, and the positional source is then used from where it is.
|
|
68
67
|
|
|
69
68
|
## The `purgetss/brand/` convention
|
|
70
69
|
|
|
@@ -121,6 +120,8 @@ You can also pass a path directly or point to a logo from the config. Useful whe
|
|
|
121
120
|
purgetss brand ./docs/snap-logo.svg
|
|
122
121
|
```
|
|
123
122
|
|
|
123
|
+
A positional logo is moved into `purgetss/brand/logo.<ext>` when no canonical logo exists yet (see [Quick start](#quick-start)). To keep a master in `docs/`, point to it from the config instead, or use a `--<piece>-logo` flag.
|
|
124
|
+
|
|
124
125
|
Or in `purgetss/config.cjs`, where each piece takes its own `logo`:
|
|
125
126
|
|
|
126
127
|
```javascript
|
|
@@ -236,7 +237,7 @@ And these live at the top level:
|
|
|
236
237
|
|
|
237
238
|
| Key | Default | Purpose |
|
|
238
239
|
| --- | --- | --- |
|
|
239
|
-
| `background` | `'#FFFFFF'` | The background every piece inherits unless it declares its own: the Android adaptive background layer, the `DefaultIcon-ios.png` flatten, the splash canvases, and the marketplace flatten
|
|
240
|
+
| `background` | `'#FFFFFF'` | The background every piece inherits unless it declares its own: the Android adaptive background layer, the `DefaultIcon-ios.png` flatten, the splash canvases, and the marketplace flatten. |
|
|
240
241
|
| `artworkCornerRadius` | `'0%'` | Shared radius for the four supported non-icon artwork pieces. It never changes store or launcher icons. |
|
|
241
242
|
| `splashCornerRadius` | — | Optional splash-only override for `iosSplash` and `androidSplash`. |
|
|
242
243
|
| `confirmOverwrites` | `true` | Whether `brand` asks before overwriting project files in place. |
|
|
@@ -304,7 +305,7 @@ The output is automatically routed to the right directory for your project layou
|
|
|
304
305
|
|
|
305
306
|
```text
|
|
306
307
|
<project>/
|
|
307
|
-
├── DefaultIcon.png ← 1024×1024, universal fallback (brand.icon.padding)
|
|
308
|
+
├── DefaultIcon.png ← 1024×1024, universal fallback, opaque (brand.icon.padding)
|
|
308
309
|
├── DefaultIcon-ios.png ← 1024×1024, iOS flattened on brand.background
|
|
309
310
|
├── DefaultIcon-Dark.png ← 1024×1024, iOS 18+ dark (transparent per Apple HIG)
|
|
310
311
|
├── DefaultIcon-Tinted.png ← 1024×1024, iOS 18+ tinted (grayscale on black)
|
|
@@ -531,7 +532,7 @@ The second one colors the notification's accent, not the icon. Create or merge `
|
|
|
531
532
|
|
|
532
533
|
Titanium builds `LaunchLogo.imageset` itself on every iOS build, resizing one source into the five sizes it needs. It looks for `LaunchLogo.png` first and falls back to `DefaultIcon.png`.
|
|
533
534
|
|
|
534
|
-
So there is nothing for PurgeTSS to generate there, but there is something to choose. With only `DefaultIcon.png` around, the launch screen shows your app icon
|
|
535
|
+
So there is nothing for PurgeTSS to generate there, but there is something to choose. With only `DefaultIcon.png` around, the launch screen shows your app icon with its configured icon inset (`0%` by default). Dropping a `logo-launch.svg` (or `.png`) into `purgetss/brand/` makes `brand` write a `LaunchLogo.png`, and the launch screen shows the full logotype instead:
|
|
535
536
|
|
|
536
537
|
```bash
|
|
537
538
|
cp docs/my-wordmark.svg purgetss/brand/logo-launch.svg
|
|
@@ -580,7 +581,7 @@ purgetss brand --no-dark --no-tinted
|
|
|
580
581
|
> Upstream work in progress
|
|
581
582
|
> 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).
|
|
582
583
|
>
|
|
583
|
-
> Until that
|
|
584
|
+
> Until that issue is resolved, 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 is resolved, the command becomes fully end-to-end.
|
|
584
585
|
|
|
585
586
|
## Brand color
|
|
586
587
|
|
|
@@ -598,7 +599,7 @@ purgetss brand --bg-color "#0B1326"
|
|
|
598
599
|
|
|
599
600
|
Any piece can opt out with its own `background`. That is what `dark: { background: null }` does in the default config, which keeps `DefaultIcon-Dark.png` transparent per Apple HIG.
|
|
600
601
|
|
|
601
|
-
If you never pass the flag, background stays `#FFFFFF`. `iTunesConnect.png
|
|
602
|
+
If you never pass the flag, background stays `#FFFFFF`. `iTunesConnect.png`, `MarketplaceArtwork.png` and `MarketplaceArtworkFeature.png` are always flattened to opaque output on the resolved background.
|
|
602
603
|
|
|
603
604
|
### Matching the launch background
|
|
604
605
|
|
|
@@ -606,93 +607,9 @@ If you never pass the flag, background stays `#FFFFFF`. `iTunesConnect.png` and
|
|
|
606
607
|
|
|
607
608
|
`purgetss brand --notes` prints that setup with the project's current `brand.background` already filled in.
|
|
608
609
|
|
|
610
|
+
## Padding and geometry
|
|
609
611
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
Padding belongs to the piece, not to the project. Each piece has its own key and its own flag:
|
|
613
|
-
|
|
614
|
-
| Piece | Config | Flag | Default |
|
|
615
|
-
| --- | --- | --- | --- |
|
|
616
|
-
| `adaptive` | `brand.adaptive.padding` | `--android-adaptive-padding` | `18%` |
|
|
617
|
-
| `legacy-icon` | `brand.legacyIcon.padding` | `--android-legacy-padding` | `10%` |
|
|
618
|
-
| `appicon` | `brand.appicon.padding` | `--appicon-padding` | `10%` |
|
|
619
|
-
| `icon` | `brand.icon.padding` | `--ios-padding` | `0%` |
|
|
620
|
-
| `feature-graphic` | `brand.featureGraphic.padding` | `--feature-graphic-padding` | `12%` |
|
|
621
|
-
| `launch-logo` | `brand.launchLogo.padding` | `--launch-logo-padding` | `12%` |
|
|
622
|
-
| `android-splash` | `brand.androidSplash.padding` | `--android-splash-padding` | `26%` |
|
|
623
|
-
| `ios-splash` | `brand.iosSplash.padding` | `--ios-splash-padding` | `26%` |
|
|
624
|
-
|
|
625
|
-
`--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.
|
|
626
|
-
|
|
627
|
-
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`.
|
|
628
|
-
|
|
629
|
-
### How the source is read, and how sharp the output is
|
|
630
|
-
|
|
631
|
-
Two things are worth knowing about what happens to your `logo.svg` or `logo.png` before any padding is applied.
|
|
632
|
-
|
|
633
|
-
**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.
|
|
634
|
-
|
|
635
|
-
**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:
|
|
636
|
-
|
|
637
|
-
```text
|
|
638
|
-
• Masters at 942 px — the largest any selected piece asks for
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
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, never an upscale.
|
|
642
|
-
|
|
643
|
-
The one case this cannot fix is a raster source that is simply too small: a 512-px PNG cannot produce a sharp 942-px icon. Prefer SVG, or a PNG of at least 1024×1024.
|
|
644
|
-
|
|
645
|
-
### Splash padding
|
|
646
|
-
|
|
647
|
-
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.
|
|
648
|
-
|
|
649
|
-
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.
|
|
650
|
-
|
|
651
|
-
| `androidSplash.padding` / `iosSplash.padding` | Logo | `default.png` (1440×2560) | `res-notlong-port-mdpi` (320×480) |
|
|
652
|
-
| --- | --- | --- | --- |
|
|
653
|
-
| `20%` | 60% of the shorter side | 864 px | 192 px |
|
|
654
|
-
| `26%` (default) | 48% | 691 px | 153 px |
|
|
655
|
-
| `30%` | 40% | 576 px | 128 px |
|
|
656
|
-
| `35%` | 30% | 432 px | 96 px |
|
|
657
|
-
|
|
658
|
-
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.
|
|
659
|
-
|
|
660
|
-
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.
|
|
661
|
-
|
|
662
|
-
## Rounded non-icon artwork
|
|
663
|
-
|
|
664
|
-
`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.
|
|
665
|
-
|
|
666
|
-
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.
|
|
667
|
-
|
|
668
|
-
Precedence is:
|
|
669
|
-
|
|
670
|
-
- Feature Graphic / LaunchLogo: piece-specific flag → `--artwork-corner-radius` → piece config → `brand.artworkCornerRadius` → `0%`.
|
|
671
|
-
- Legacy splashes: platform flag → `--splash-corner-radius` → `--artwork-corner-radius` → piece config → `brand.splashCornerRadius` → `brand.artworkCornerRadius` → `0%`.
|
|
672
|
-
|
|
673
|
-
Store and launcher icons remain unmasked for platform processing. `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.
|
|
674
|
-
|
|
675
|
-
### Adaptive icon padding
|
|
676
|
-
|
|
677
|
-
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:
|
|
678
|
-
|
|
679
|
-
| Padding | Logo | vs. the 66 dp safe circle | vs. the ~72 dp the mask shows |
|
|
680
|
-
| --- | --- | --- | --- |
|
|
681
|
-
| `15%` | 75.6 dp | outside | **outside** — clipped on any launcher |
|
|
682
|
-
| `16%` | 73.4 dp | outside | **outside** |
|
|
683
|
-
| `18%` | 69.1 dp | corners outside | inside — **the default** |
|
|
684
|
-
| `19.44%` | 66.0 dp | exactly on it | inside |
|
|
685
|
-
| `20%` | 64.8 dp | inside | inside — most conservative |
|
|
686
|
-
|
|
687
|
-
`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. Drop to `20%` if your mark runs edge to edge and you see clipping on a circular launcher.
|
|
688
|
-
|
|
689
|
-
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.
|
|
690
|
-
|
|
691
|
-
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.
|
|
692
|
-
|
|
693
|
-
### Legacy icon padding
|
|
694
|
-
|
|
695
|
-
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%`.
|
|
612
|
+
Per-piece padding, how the source is read, the shared splash sizing rule, rounded non-icon artwork and the adaptive-mask math are in [Brand Padding & Geometry](./brand-padding-geometry.md).
|
|
696
613
|
|
|
697
614
|
## Cleanup legacy branding artifacts
|
|
698
615
|
|
|
@@ -765,6 +682,28 @@ Adaptive padding is probably too generous. Lower it:
|
|
|
765
682
|
purgetss brand --android-adaptive-padding 17
|
|
766
683
|
```
|
|
767
684
|
|
|
685
|
+
### A white frame appears around a dark icon
|
|
686
|
+
|
|
687
|
+
PurgeTSS does not require a white frame. Square iOS/store pieces (`icon`, `dark`, `tinted`, `marketplace`) default to `0%` since v7.14.0, so finished edge-to-edge artwork stays full-bleed. If an older or customized config still has `brand.icon.padding: '4%'`, change it to `0%` and regenerate.
|
|
688
|
+
|
|
689
|
+
Android launcher pieces keep safe-zone padding on purpose (`18%` adaptive, `10%` legacy and appicon). If the source is already a finished opaque icon, that inset exposes `brand.background`. Set the inherited background once to a color matching the artwork's perimeter, or give the Android pieces a transparent mark instead of the finished icon canvas:
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
brand: {
|
|
693
|
+
background: '#020109',
|
|
694
|
+
icon: { padding: '0%' },
|
|
695
|
+
adaptive: { logo: './purgetss/brand/logo-adaptive.png', padding: '18%' },
|
|
696
|
+
legacyIcon: { padding: '10%' },
|
|
697
|
+
appicon: { padding: '10%' }
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
When the main logo is opaque to its edges and a padded piece sits on a contrasting background, `brand` warns before generating the pieces (it checks `icon`, `marketplace`, `adaptive`, `legacy-icon` and `appicon`): `The source is opaque to its edges (edge color ≈ #…)` followed by `A contrasting frame will be visible in: …`, naming each affected piece with its padding and background. Pieces with their own `logo` are not checked.
|
|
702
|
+
|
|
703
|
+
### "The source logo is not close to square"
|
|
704
|
+
|
|
705
|
+
`brand` prints this warning when the main logo's aspect ratio is wider than 1.25:1 or taller than 1:1.25. Launcher icons and the Android 12+ system splash center the mark inside a mask, so a wordmark comes out cramped. Drop a square `logo-adaptive.svg` (and `logo-splash-icon.svg` if you use `--splash-icon`) as described in [Community-Discovered Patterns](#community-discovered-patterns).
|
|
706
|
+
|
|
768
707
|
### The monochrome version looks like a white blob
|
|
769
708
|
|
|
770
709
|
Your colored logo likely has multi-color detail that does not survive automatic whitening. Provide a dedicated silhouette:
|
|
@@ -784,7 +723,7 @@ Apple requires App Store icons to have no alpha channel. `DefaultIcon-ios.png` i
|
|
|
784
723
|
|
|
785
724
|
### The dark variant doesn't show on my iPhone
|
|
786
725
|
|
|
787
|
-
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
|
|
726
|
+
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 issue is resolved, you may need to add `DefaultIcon-Dark.png` and `DefaultIcon-Tinted.png` manually into the Xcode appiconset after the first iOS build.
|
|
788
727
|
|
|
789
728
|
### I get "Input image exceeds pixel limit" on an SVG from Affinity / Illustrator
|
|
790
729
|
|
|
@@ -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).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- [
|
|
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.**
|
|
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
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
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).
|