diff --git a/GLOSSARY.md b/GLOSSARY.md
index a3fc82f..edf456f 100644
--- a/GLOSSARY.md
+++ b/GLOSSARY.md
@@ -11,7 +11,7 @@ app: design, Exercise Library, Plans and History. The last-used Mode is remember
_Avoid_: Tab, section, category
**Stretching Mode**:
-The calm Mode: light design, sage/teal, rounded shapes, slow transitions.
+The calm Mode: light design, lavender and violet, rounded shapes, slow transitions.
**Workout Mode**:
The energetic Mode: dark design, orange/red on near-black, bold type, snappy transitions.
diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..945dfda
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..a5c57ce
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
index f74085f..ab7b2e7 100644
--- a/android/app/src/main/res/drawable-v21/launch_background.xml
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -1,12 +1,5 @@
-
+
-
-
-
-
+
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..0984003
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ec9463a
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1d6f855
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
index 304732f..ab7b2e7 100644
--- a/android/app/src/main/res/drawable/launch_background.xml
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -1,12 +1,5 @@
-
+
-
-
-
-
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c79c58a
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..eb458e0 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..e46c937 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..645d405 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..aa47ae1 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..3386a2e 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..e092aba
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #F1EEF6
+ #F1EEF6
+
\ No newline at end of file
diff --git a/assets/icon/icon.png b/assets/icon/icon.png
new file mode 100644
index 0000000..5ac7172
Binary files /dev/null and b/assets/icon/icon.png differ
diff --git a/assets/icon/icon_foreground.png b/assets/icon/icon_foreground.png
new file mode 100644
index 0000000..b2492b4
Binary files /dev/null and b/assets/icon/icon_foreground.png differ
diff --git a/docs/adr/0002-library-ui-and-player.md b/docs/adr/0002-library-ui-and-player.md
index 0d4b820..0ae0a2b 100644
--- a/docs/adr/0002-library-ui-and-player.md
+++ b/docs/adr/0002-library-ui-and-player.md
@@ -13,7 +13,7 @@ Status: Accepted (2026-10-28)
Stretching = hold, both-sides flag, rounds. Workout Steps can be grouped into Supersets.
5. **Library browsing**: search + muscle/equipment/level filters, reused as the Plan exercise picker.
6. **Structure & design**: Mode switch at top; per-mode tabs Plans · Library · History.
- Stretching = calm/light (sage/teal, rounded, slow). Workout = dark/energetic (orange/red, bold, snappy).
+ Stretching = calm/light (lavender/violet since ADR 0007, first sage/teal; rounded, slow). Workout = dark/energetic (orange/red, bold, snappy).
Last-used mode is remembered.
7. **Player cues**: Visual cues (countdown ring, accent pulse, next cards, auto-advance, screen kept on)
plus a gentle, non-annoying sound. No vibration.
diff --git a/docs/adr/0007-app-icon.md b/docs/adr/0007-app-icon.md
new file mode 100644
index 0000000..5f5d16a
--- /dev/null
+++ b/docs/adr/0007-app-icon.md
@@ -0,0 +1,23 @@
+# ADR 0007 — App icon: a lotus in a Zen circle, with a hint of lifting
+
+Status: Accepted (2026-10-29)
+
+1. **Motif**: a lotus inside an ensō (Zen brush circle). Yoga and calm lead; lifting is only a hint:
+ a small weight plate floating in the circle's open gap, and a small barbell under the lotus.
+2. **Brush**: the ensō is painted counter-clockwise from the top, with its gap at the upper right.
+ It starts round and solid with calm, nearly smooth edges, then runs dry: pale streaks open inside
+ the stroke and the bristles give out at different points, so the tail frays out close to the plate.
+ The circle is never closed.
+3. **Colours**: a calm purple palette: lavender mist ground `#F1EEF6`, muted violet `#6B5B95` for the
+ ensō and the lotus's centre petal, light violet `#A496C8` for the side petals, and a soft apricot
+ `#E98A6F` only for the plate and the barbell.
+4. **Stretching follows the icon**: Stretching Mode's palette moved from sage/teal to the same lavender
+ and violet, so the app and its icon match. Workout keeps its dark ember design.
+5. **Style**: flat shapes, no text, no gradients or outlines.
+6. **Format**: Android adaptive icon (foreground on a solid lavender background, masked by the launcher)
+ plus a legacy square PNG, wired up with `flutter_launcher_icons` as in CookIT.
+7. **Launch screen**: the Android launch background uses the same lavender mist as the icon.
+
+The icon is generated by `tools/icon/make_icon.py` (seeded, so it redraws identically): run it, then
+`dart run flutter_launcher_icons`. Rejected along the way: a dumbbell or barbell as the main element,
+a plate closing the circle's gap, a seated figure, and heavier, more textured brush strokes.
diff --git a/lib/src/design/zen_style.dart b/lib/src/design/zen_style.dart
index e7e787d..e8a83e2 100644
--- a/lib/src/design/zen_style.dart
+++ b/lib/src/design/zen_style.dart
@@ -74,16 +74,17 @@ class ZenStyle extends ThemeExtension {
static const stretching = ZenStyle(
mode: Mode.stretching,
brightness: Brightness.light,
- background: Color(0xFFEDF3EF),
+ // Lavender mist and a muted violet, as in the app icon.
+ background: Color(0xFFF1EEF6),
surface: Color(0xFFFFFFFF),
- surfaceSoft: Color(0xFFDCEAE2),
- ink: Color(0xFF1E3832),
- inkMuted: Color(0xFF587069),
- accent: Color(0xFF2E8B80),
- accentEnd: Color(0xFF2E8B80),
+ surfaceSoft: Color(0xFFE4DEEF),
+ ink: Color(0xFF2A2338),
+ inkMuted: Color(0xFF6A6279),
+ accent: Color(0xFF6B5B95),
+ accentEnd: Color(0xFF6B5B95),
onAccent: Color(0xFFFFFFFF),
- line: Color(0xFFA8C9B4),
- navBar: Color(0xFFD3E5DA),
+ line: Color(0xFFC3B8DA),
+ navBar: Color(0xFFE0D9EC),
radius: 28,
controlRadius: 999,
motion: Duration(milliseconds: 700),
diff --git a/pubspec.lock b/pubspec.lock
index 1ec51be..8b8003a 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.1"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "0d58d1fa1fddf6588251ccf2819f3fb3b6d0452c1b2813ac7413a05592bfbefe"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.2"
args:
dependency: transitive
description:
@@ -294,6 +302,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.4"
flutter_lints:
dependency: "direct dev"
description:
@@ -397,6 +413,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.12.0"
intl:
dependency: "direct main"
description:
@@ -613,6 +637,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.3"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "613646d076944f3acc125830af935b48d4b8a57a388691547bbfd25b434f4d63"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.5.4"
provider:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 1e15bc0..1e9a7e5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -31,6 +31,7 @@ dev_dependencies:
drift_dev: ^2.31.0
build_runner: ^2.15.1
sqlite3: ^2.9.4
+ flutter_launcher_icons: ^0.14.4
flutter:
uses-material-design: true
@@ -84,3 +85,12 @@ flutter:
weight: 600
- asset: assets/fonts/Barlow-Bold.ttf
weight: 700
+
+# Drawn by tools/icon/make_icon.py; regenerate with `dart run flutter_launcher_icons`.
+flutter_launcher_icons:
+ android: true
+ ios: false
+ image_path: assets/icon/icon.png
+ adaptive_icon_background: "#F1EEF6"
+ adaptive_icon_foreground: assets/icon/icon_foreground.png
+ min_sdk_android: 21
diff --git a/tools/icon/make_icon.py b/tools/icon/make_icon.py
new file mode 100644
index 0000000..a325469
--- /dev/null
+++ b/tools/icon/make_icon.py
@@ -0,0 +1,236 @@
+"""Draws the ZenXercise app icon: a lotus inside a dry-brush ensō, with a hint of lifting.
+
+Usage: python make_icon.py [output folder] (default: assets/icon)
+
+Writes icon.png (legacy square icon on the lavender ground) and icon_foreground.png
+(transparent adaptive-icon foreground, kept inside the launcher's safe zone).
+Everything is drawn at 4x and scaled down, which smooths the edges. All randomness is
+seeded, so the icon is identical on every run.
+"""
+
+from __future__ import annotations
+
+import math
+import random
+import sys
+from pathlib import Path
+
+from PIL import Image, ImageDraw
+
+SIZE = 1024
+SCALE = 4
+# Stretching's palette.
+GROUND = (241, 238, 246, 255) # Lavender mist: the icon's background and the launch screen
+VIOLET = (107, 91, 149, 255) # The ensō and the lotus's centre petal
+VIOLET_LIGHT = (164, 150, 200, 255) # Side petals: a tonal step of the violet
+APRICOT = (233, 138, 111, 255) # The lifting hint only: the plate and the small barbell
+
+
+def _s(v: float) -> float:
+ return v * SCALE
+
+
+class _Noise:
+ """Smooth 1-D value noise in -1..1."""
+
+ def __init__(self, seed: int, size: int = 512):
+ rng = random.Random(seed)
+ self.values = [rng.uniform(-1, 1) for _ in range(size)]
+
+ def __call__(self, x: float) -> float:
+ i = math.floor(x)
+ f = x - i
+ a = self.values[i % len(self.values)]
+ b = self.values[(i + 1) % len(self.values)]
+ return a + (b - a) * (1 - math.cos(math.pi * f)) / 2
+
+
+def dry_brush_enso(
+ draw: ImageDraw.ImageDraw, cx: float, cy: float, r: float, width: float, gap_deg: float, gap_at: float
+):
+ """An ensō painted counter-clockwise from beside the gap.
+
+ The opening of [gap_deg] degrees is centred on [gap_at] (degrees, 0 = right, counter-clockwise).
+ The brush lands with a round start and calm, nearly smooth edges; from halfway it runs dry:
+ its bristles stop overlapping, so pale streaks open inside the stroke, and the bristles give
+ out at different points, fraying the tail.
+ """
+ serration, nicks, swell, wander = _Noise(3), _Noise(29), _Noise(11), _Noise(19)
+ rng = random.Random(13)
+ start = math.radians(gap_at + gap_deg / 2)
+ sweep = math.radians(360 - gap_deg)
+ dry = 0.5 # Where the brush starts to run out of ink.
+
+ def angle(t: float) -> float:
+ return start + sweep * t
+
+ def centre(t: float) -> float:
+ return r * (1 + 0.01 * wander(t * 3))
+
+ def stroke_width(t: float) -> float:
+ return width * (0.93 + 0.1 * swell(t * 3.5)) * (1 - 0.28 * (max(0.0, t - dry) / (1 - dry)) ** 1.6)
+
+ def point(t: float, d: float) -> tuple[float, float]:
+ a = angle(t)
+ return (_s(cx + d * math.cos(a)), _s(cy - d * math.sin(a)))
+
+ def ragged(t: float, side: int) -> float:
+ """A faint irregularity on both edges, and the rare small nick on the outside."""
+ fine = 0.006 * serration(t * 40 + side * 31) + 0.002 * serration(t * 120 + side * 77)
+ nick = max(0.0, nicks(t * 9 + side * 5) - 0.8) * (0.03 if side > 0 else 0.0)
+ return (fine - nick) * min(1.0, t / 0.08)
+
+ steps = 1400
+ # Solid body, overlapping the bristles where they take over, with a round start.
+ ts = [i * (dry + 0.06) / steps for i in range(steps + 1)]
+ outer = [point(t, centre(t) + stroke_width(t) * (0.5 + ragged(t, 1))) for t in ts]
+ inner = [point(t, centre(t) - stroke_width(t) * (0.5 + ragged(t, -1))) for t in ts]
+ draw.polygon(outer + inner[::-1], fill=VIOLET)
+ a0, w0, d0 = angle(0.0), stroke_width(0.0), centre(0.0)
+ x0, y0 = cx + d0 * math.cos(a0), cy - d0 * math.sin(a0)
+ draw.ellipse([_s(x0 - w0 / 2), _s(y0 - w0 / 2), _s(x0 + w0 / 2), _s(y0 + w0 / 2)], fill=VIOLET)
+
+ # Bristles from the dry point on.
+ count = 15
+ for k in range(count):
+ frac = -0.5 + (k + 0.5) / count
+ edge = abs(frac) * 2
+ ink = rng.random()
+ end = 0.95 - 0.05 * rng.random() - 0.16 * edge**1.6 # Edge bristles give out sooner.
+ skips = []
+ for _ in range(rng.randint(0, 2)):
+ s0 = dry + 0.22 + rng.random() * max(0.0, end - dry - 0.3)
+ skips.append((s0, s0 + 0.008 + rng.random() * 0.025 * (1.2 - ink)))
+ phase = rng.random() * math.tau
+
+ def edges(t: float) -> tuple[float, float]:
+ w = stroke_width(t)
+ dried = max(0.0, (t - dry) / (end - dry))
+ # Full overlap while inked; separating as the bristle dries out.
+ half = 0.5 / count * (1.8 - 1.25 * dried ** (0.7 + ink)) * max(0.0, min(1.0, (end - t) / 0.12)) ** 0.8
+ # Lifting off the paper and touching down again: taper into and out of each skip.
+ for a, b in skips:
+ near = max(a - t, t - b, 0.0)
+ half *= min(1.0, near / 0.018) ** 0.7
+ splay = w * 0.005 * dried * math.sin(2 * math.pi * 6 * t + phase)
+ mid = centre(t) + w * frac + splay
+ # Never past the stroke's own edges, so the body hands over without a step.
+ top = min(mid + w * half, centre(t) + w * (0.5 + ragged(t, 1)))
+ bottom = max(mid - w * half, centre(t) - w * (0.5 + ragged(t, -1)))
+ return top, min(bottom, top)
+
+ run: list[float] = []
+
+ def flush():
+ if len(run) > 1:
+ draw.polygon([point(t, edges(t)[0]) for t in run] + [point(t, edges(t)[1]) for t in reversed(run)], fill=VIOLET)
+ run.clear()
+
+ for i in range(steps + 1):
+ t = dry + (end - dry) * i / steps
+ if any(a <= t <= b for a, b in skips):
+ flush()
+ else:
+ run.append(t)
+ flush()
+
+
+def plate(draw: ImageDraw.ImageDraw, cx: float, cy: float, r: float, ground):
+ """A weight plate seen from the front: a disc with a centre hole."""
+ draw.ellipse([_s(cx - r), _s(cy - r), _s(cx + r), _s(cy + r)], fill=APRICOT)
+ hole = r * 0.3
+ draw.ellipse([_s(cx - hole), _s(cy - hole), _s(cx + hole), _s(cy + hole)], fill=ground)
+
+
+class Pen:
+ """Draws in 1024-unit coordinates around the centre, shrunk by [scale]."""
+
+ def __init__(self, draw: ImageDraw.ImageDraw, scale: float):
+ self.draw = draw
+ self.scale = scale
+
+ def p(self, x: float, y: float) -> tuple[float, float]:
+ c = SIZE / 2
+ return (_s(c + (x - c) * self.scale), _s(c + (y - c) * self.scale))
+
+ def w(self, v: float) -> float:
+ return _s(v * self.scale)
+
+ def circle(self, x, y, r, fill):
+ cx, cy = self.p(x, y)
+ rr = self.w(r)
+ self.draw.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=fill)
+
+ def limb(self, points, width, fill):
+ """A thick stroke through [points] with round ends and joints."""
+ pts = [self.p(x, y) for x, y in points]
+ self.draw.line(pts, fill=fill, width=int(self.w(width)), joint="curve")
+ for x, y in points:
+ self.circle(x, y, width / 2, fill)
+
+ def bar(self, x0, x1, y, thickness, fill, plates=1.0):
+ """A horizontal barbell: the bar, a big and a small plate at each end, sized by [plates]."""
+ self.limb([(x0, y), (x1, y)], thickness, fill)
+ for side, end in ((-1, x0), (1, x1)):
+ for offset, w, h in ((-28 * plates, 34 * plates, 120 * plates), (4 * plates, 24 * plates, 84 * plates)):
+ px = end - side * offset
+ a, b = self.p(px - w / 2, y - h / 2)
+ c, d = self.p(px + w / 2, y + h / 2)
+ self.draw.rounded_rectangle([a, b, c, d], radius=self.w(w / 2.4), fill=fill)
+
+ def petal(self, bx, by, length, width, angle_deg, fill):
+ """An almond-shaped petal growing from (bx, by) towards [angle_deg] (90 = up)."""
+ a = math.radians(angle_deg)
+ ux, uy = math.cos(a), -math.sin(a)
+ nx, ny = -uy, ux
+ left, right = [], []
+ steps = 80
+ for i in range(steps + 1):
+ t = i / steps
+ half = width / 2 * math.sin(math.pi * t) ** 0.75 * (1 - 0.25 * t)
+ x, y = bx + ux * length * t, by + uy * length * t
+ left.append(self.p(x + nx * half, y + ny * half))
+ right.append(self.p(x - nx * half, y - ny * half))
+ self.draw.polygon(left + right[::-1], fill=fill)
+
+
+def lotus(pen: Pen, base: float) -> None:
+ """Five petals growing from (512, [base]): a deep centre, lighter sides."""
+ for angle, length, width, fill in (
+ (162, 250, 150, VIOLET_LIGHT),
+ (18, 250, 150, VIOLET_LIGHT),
+ (128, 300, 160, VIOLET_LIGHT),
+ (52, 300, 160, VIOLET_LIGHT),
+ (90, 360, 190, VIOLET),
+ ):
+ pen.petal(512, base, length, width, angle, fill)
+
+
+def draw_icon(ground, scale: float) -> Image.Image:
+ """The motif on [ground]; [scale] shrinks it to fit the adaptive safe zone."""
+ image = Image.new("RGBA", (SIZE * SCALE, SIZE * SCALE), ground)
+ draw = ImageDraw.Draw(image)
+ c = SIZE / 2
+ r = 300 * scale
+ plate_at = 58 # Upper right, in the ensō's open gap.
+ # The stroke starts 29° past the plate and runs on until its dry tail gives out close to it.
+ dry_brush_enso(draw, c, c, r, 74 * scale, gap_deg=44, gap_at=plate_at + 7)
+ lotus(Pen(draw, scale * 0.7), base=660)
+ Pen(draw, scale * 0.7).bar(382, 642, 730, 16, APRICOT, plates=0.5)
+ a = math.radians(plate_at)
+ plate(draw, c + r * math.cos(a), c - r * math.sin(a), 46 * scale, ground)
+ return image.resize((SIZE, SIZE), Image.LANCZOS)
+
+
+def main() -> None:
+ out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[2] / "assets" / "icon"
+ out.mkdir(parents=True, exist_ok=True)
+ # Legacy icon: full bleed on the lavender ground.
+ draw_icon(GROUND, 1.0).save(out / "icon.png")
+ # Adaptive foreground: transparent, shrunk into the central safe zone (about 61% of the canvas).
+ draw_icon((0, 0, 0, 0), 0.68).save(out / "icon_foreground.png")
+ print(f"icon -> {out}")
+
+
+if __name__ == "__main__":
+ main()