11 Commits

Author SHA1 Message Date
semantic-release-bot
266fdc18a8 chore(release): 1.1.0 [skip ci]
# [1.1.0](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.3...v1.1.0) (2026-07-20)

### Features

* **home:** custom SVG zone icons for body-zone cards ([#15](#15)) ([8f10fe7](8f10fe763d))
2026-07-20 12:32:23 +00:00
8f10fe763d feat(home): custom SVG zone icons for body-zone cards (#15)
All checks were successful
Admin Web Docker / Admin Web Tests (push) Successful in 33s
CI / Detect Changes (push) Successful in 4s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Semantic Release (push) Successful in 10s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 53s
CI / Deploy (push) Has been skipped
2026-07-20 14:31:34 +02:00
semantic-release-bot
c5b2538a60 chore(release): 1.0.3 [skip ci]
## [1.0.3](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.2...v1.0.3) (2026-07-20)

### Bug Fixes

* **ci:** pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | ([#14](#14)) ([ee2da59](ee2da592cc))
* **ci:** pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | ([#14](#14)) ([e4b97b4](e4b97b4d14))
2026-07-20 07:52:11 +00:00
ee2da592cc fix(ci): pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | (#14)
All checks were successful
Admin Web Docker / Admin Web Tests (push) Successful in 33s
Admin Web Docker / Semantic Release (push) Successful in 10s
CI / YouTube Worker (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 1m1s
CI / Deploy (push) Has been skipped
2026-07-20 09:51:21 +02:00
e4b97b4d14 fix(ci): pr-iphone-deploy.yml fails to parse — indent python3 body inside run: | (#14)
Some checks failed
CI / Deploy (push) Has been cancelled
Admin Web Docker / Docker Build Validation (push) Has been cancelled
Admin Web Docker / Semantic Release (push) Has been cancelled
Admin Web Docker / Build & Push Docker Image (push) Has been cancelled
Admin Web Docker / Admin Web Tests (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / YouTube Worker (push) Has been cancelled
2026-07-20 09:51:21 +02:00
semantic-release-bot
7a1a70af87 chore(release): 1.0.2 [skip ci]
## [1.0.2](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.1...v1.0.2) (2026-07-19)

### Bug Fixes

* **ci:** pr-iphone-deploy no longer self-merges on its own comment ([98f4f82](98f4f82db2)), closes [#6937a36](https://gitea.1000co.fr/millianlmx/tabatago/issues/6937a36) [#12](#12)
* **ci:** replace trigger-level paths: with dorny/paths-filter (Gitea ignores on.pull_request.paths) ([27f9c6b](27f9c6b7b6)), closes [#13](#13)
2026-07-19 20:17:03 +00:00
e2532a8136 Merge pull request 'fix(ci): pr-iphone-deploy no longer self-merges on its own comment' (#13) from fix/ci-lgtm-self-match into main
All checks were successful
CI / Detect Changes (push) Successful in 4s
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Semantic Release (push) Successful in 12s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 53s
CI / Deploy (push) Has been skipped
Admin Web Docker / Admin Web Tests (push) Successful in 33s
Reviewed-on: #13
2026-07-19 22:16:12 +02:00
Millian Lamiaux
27f9c6b7b6 fix(ci): replace trigger-level paths: with dorny/paths-filter (Gitea ignores on.pull_request.paths)
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped
Gitea Actions does not reliably honor `on.pull_request.paths:` filters —
the workflow silently fails to trigger even when changed files match the
filter. PR #13 touches .github/workflows/pr-iphone-deploy.yml (explicitly
listed in the paths filter) and AGENTS.md, but only ci.yml fired.

The repo history shows this was already known: 65d85b6 'remplacer paths
trigger par dorny/paths-filter' moved to job-level filtering, but later
commits reverted to the trigger-level paths: block. The proven-working
pattern already lives in ci.yml (dorny/paths-filter@v3 in a 'changes' job).

Fix (mirror ci.yml):
- Remove trigger-level `paths:` from on.pull_request.
- Add a 'changes' job (ubuntu-latest, dorny/paths-filter@v3, single 'ios'
  filter covering tabatago-swift/** and the workflow itself).
- Gate build-deploy: needs: changes + if: needs.changes.outputs.ios == 'true'.
- Gate wait-approval: if: needs.build-deploy.result == 'success' so a
  filtered-out deploy doesn't launch a merge poll against an undeployed PR.

Everything else unchanged: concurrency, squash-merge, 2h timeout, the
self-merge fix from the previous commit on this branch (sentinel marker
+ python3 parser). This PR will self-validate: it touches the workflow
file, so the ios filter matches, so the deploy runs.
2026-07-19 22:05:28 +02:00
Millian Lamiaux
98f4f82db2 fix(ci): pr-iphone-deploy no longer self-merges on its own comment
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped
The build-deploy job posts a 'Prêt à tester' comment whose body literally
contains 'Reply **LGTM** pour merger' and 'Reply **KO**' as instructions to
the reviewer. wait-approval re-fetches ALL comments every 30s and greps any
body for \bLGTM\b (checked before KO) — so on the first poll (~30s after
deploy) the bot matched its OWN comment and squash-merged automatically,
with no human review. Regressed in #6937a36 (PR #12) which tightened the
regex to catch 'Tested, LGTM!' but didn't notice the bot body matched.

Fix (sentinel marker + python parse, identity-agnostic so it's safe whether
PR_API_TOKEN is a bot or a personal account):

- build-deploy: embed <!-- tabatago:ready-to-test --> in the bot comment
  (HTML comment, invisible in rendered markdown, present in raw body).
- wait-approval: replace the fragile grep-over-JSON with a python3 heredoc
  using json.loads + the existing \bLGTM\b / \bKO\b regexes. Skip any
  comment whose body contains the sentinel marker. Check KO BEFORE LGTM so
  a PR with both signals blocks (matches the existing 'KO blocks' intent).
  LGTM still does {"Do":"squash"}; KO still blocks; 2h timeout unchanged.

Verified locally with three scenarios:
  bot-only       -> PENDING  (was LGTM before — the bug)
  bot + reviewer -> LGTM     (squash-merge still fires)
  bot + KO       -> KO       (block still fires, even with bot LGTM in history)

AGENTS.md §5 step 9, §5 pitfalls, and §8 anti-patterns updated so the doc
describes the marker rule and warns against re-introducing the self-match.
2026-07-19 21:14:59 +02:00
semantic-release-bot
1ada238004 chore(release): 1.0.1 [skip ci]
## [1.0.1](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.0...v1.0.1) (2026-07-19)

### Bug Fixes

* **ci:** pr-iphone-deploy — cache, secrets, LGTM regex, concurrency, watch build, scope ([#12](#12)) ([6937a36](6937a36ccd))
2026-07-19 19:00:24 +00:00
6937a36ccd fix(ci): pr-iphone-deploy — cache, secrets, LGTM regex, concurrency, watch build, scope (#12)
All checks were successful
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Admin Web Tests (push) Successful in 33s
CI / Detect Changes (push) Successful in 3s
Admin Web Docker / Semantic Release (push) Successful in 11s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 22s
CI / Deploy (push) Has been skipped
2026-07-19 20:59:33 +02:00
13 changed files with 413 additions and 185 deletions

View File

@@ -1,24 +1,55 @@
# =============================================================================
# TabataGo — PR → Build → WiFi/USB Direct Deploy → Wait LGTM → Merge
# TabataGo — PR → Build → devicectl Deploy → Wait LGTM → Merge
# =============================================================================
name: PR → Build → WiFi/USB Deploy → LGTM
name: PR → Build → devicectl Deploy → LGTM
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# NOTE: no trigger-level `paths:` here — Gitea Actions does not reliably
# honor `on.pull_request.paths:` filters (the workflow silently fails to
# trigger even when changed files match). Path gating is done at the job
# level via dorny/paths-filter@v3 in the `changes` job below (same pattern
# as ci.yml). Docs/backend-only PRs then skip the macOS runner.
permissions:
contents: write
pull-requests: write
# Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN,
# never the runner's native GITHUB_TOKEN — so a `permissions:` block would be
# a no-op here. Intentionally omitted.
concurrency:
# Per-PR grouping: superseded pushes cancel the previous pipeline, different
# PRs still run in parallel. Prevents double-merges and wasted runner time.
group: pr-iphone-deploy-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
# ── Path filter — determines whether the macOS build+deploy is worth running ──
# Gitea Actions does not honor trigger-level `on.pull_request.paths:`, so we
# gate at the job level with dorny/paths-filter (same pattern as ci.yml).
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
ios: ${{ steps.filter.outputs.ios }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
ios:
- 'tabatago-swift/**'
- '.github/workflows/pr-iphone-deploy.yml'
build-deploy:
name: Build & Deploy to iPhone (WiFi/USB)
name: Build & Deploy to iPhone (devicectl)
needs: changes
if: needs.changes.outputs.ios == 'true'
runs-on: macos
timeout-minutes: 20
timeout-minutes: 30
steps:
- name: Checkout
@@ -42,9 +73,11 @@ jobs:
# Act runner caches
rm -rf ~/.cache/act/*/hostexecutor/tabatago-swift/build 2>/dev/null || true
# SPM caches are KEPT between runs — RevenueCat is 1.10 GiB and takes
# SPM cache is KEPT between runs — RevenueCat is ~1.10 GiB and takes
# 5+ minutes to clone; re-cloning on every CI run would waste time
# and cause random failures on the act_runner sandbox.
# and cause random failures on the act_runner sandbox. It lives at
# build/spm-cache (see -clonedSourcePackagesDirPath in the Build step)
# so NEVER `rm -rf build/` — only delete build/derived.
# Xcode DerivedData — suppression COMPLÈTE (le bundle ID a changé,
# l'ancien dossier TabataGo-* peut être ignoré par le nouveau build)
@@ -52,10 +85,12 @@ jobs:
# Module cache Xcode — des .pcm stale peuvent segfault le linker
rm -rf ~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex 2>/dev/null || true
rm -rf ~/Library/Caches/com.apple.dt.Xcode
# NOTE: do NOT `rm -rf ~/Library/Caches/com.apple.dt.Xcode` — that is the
# shared global cache; wiping it slows every subsequent build.
# Build artifacts locaux (utilisés par le step "Build app")
rm -rf build/
# Build artifacts locaux — sparing build/spm-cache (see above).
rm -rf build/derived
rm -rf build/*.log 2>/dev/null || true
rm -rf .build
rm -rf tabatago-swift/.build
rm -rf tabatago-swift/TabataGo.xcodeproj
@@ -65,11 +100,45 @@ jobs:
find . -name "Package.resolved" -delete 2>/dev/null || true
find . -name ".package.resolved" -delete 2>/dev/null || true
echo "✅ Caches nettoyés"
echo "✅ Caches nettoyés (SPM cache préservé)"
- name: Install tools (xcodegen, node, ios-deploy)
- name: Install tools (xcodegen)
run: |
brew install xcodegen node ios-deploy || true
# The act_runner shell can land under Rosetta 2 on Apple Silicon;
# Homebrew lives in /opt/homebrew (ARM prefix), so run brew natively.
# (Brew refuses to install/upgrade under Rosetta in the ARM prefix.)
# Only xcodegen is needed — node is NOT used by any step in this
# workflow, and installing it triggers brew's "installed dependents
# check" which tries to rebuild unrelated formulae (e.g. gemini-cli)
# and fails the build.
arch -arm64 brew install xcodegen
- name: Write Secrets.xcconfig from Gitea secrets
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
REVENUECAT_API_KEY: ${{ secrets.REVENUECAT_API_KEY }}
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
run: |
# Preflight: fail loudly if a secret is missing, rather than producing
# an .app that silently can't reach Supabase/RevenueCat at runtime.
for v in SUPABASE_URL SUPABASE_ANON_KEY REVENUECAT_API_KEY; do
val="$(printenv "$v")"
if [ -z "$val" ]; then
echo "❌ Secret missing: $v. Add it to Gitea repo secrets."
exit 1
fi
done
# POSTHOG_API_KEY may legitimately be empty (PostHog not yet wired).
# NOTE: heredoc body MUST start at column 0 — xcconfig keys are
# whitespace-sensitive, leading spaces would break the build setting.
cat > tabatago-swift/Config/Secrets.xcconfig <<EOF
SUPABASE_URL = ${SUPABASE_URL}
SUPABASE_ANON_KEY = ${SUPABASE_ANON_KEY}
REVENUECAT_API_KEY = ${REVENUECAT_API_KEY}
POSTHOG_API_KEY = ${POSTHOG_API_KEY}
EOF
echo "✅ Secrets.xcconfig written (values redacted)"
- name: Generate Xcode project
run: |
@@ -99,27 +168,20 @@ jobs:
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=2MJF39L8VY
- name: Install to iPhone (WiFi → USB fallback)
- name: Install to iPhone (devicectl)
run: |
UDID="00008120-000925CE3672201E"
APP=$(find build/derived -name 'TabataGo.app' -type d | head -1)
# 1. Try WiFi first (Xcode 15+ devicectl network discovery)
echo "📱 Trying WiFi install via devicectl..."
if xcrun devicectl device install app --device "$UDID" "$APP" > /tmp/devicectl.log 2>&1; then
echo "✅ Installed via WiFi"
exit 0
fi
tail -10 /tmp/devicectl.log
# 2. Fallback to USB via ios-deploy
echo "🔌 WiFi failed, trying USB..."
if ios-deploy --bundle "$APP" --id "$UDID" --justlaunch; then
echo "✅ Installed via USB"
# devicectl handles both WiFi (network) and wired (USB-C/Thunderbolt)
# discovery natively — no separate USB-fallback tool needed.
echo "📱 Installing via devicectl..."
if xcrun devicectl device install app --device "$UDID" "$APP"; then
echo "✅ Installed via devicectl"
exit 0
fi
echo "❌ Install failed (both WiFi and USB)"
echo "❌ devicectl install failed"
exit 1
@@ -130,19 +192,38 @@ jobs:
run: |
PR="${{ github.event.pull_request.number }}"
REPO="${{ github.repository }}"
# The HTML comment is invisible in Gitea's rendered markdown but is
# present in the raw body — wait-approval uses it as a sentinel to
# skip THIS bot comment when scanning for LGTM/KO (otherwise the
# instruction text "Reply LGTM pour merger" would self-trigger a
# merge ~30s after deploy). DO NOT remove or reword without also
# updating the scanner in the wait-approval job.
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB/WiFi).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n<!-- tabatago:ready-to-test -->\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
wait-approval:
name: Wait for LGTM comment
needs: build-deploy
# Only run when build-deploy actually deployed. Without this, a skipped
# build-deploy (filtered out by `changes`) would still launch this job and
# the merge poll would run against a PR that was never deployed to device.
if: needs.build-deploy.result == 'success'
runs-on: macos
timeout-minutes: 120
steps:
- name: Checkout
# Shallow clone — wait-approval only needs scripts/pr_lgtm_scanner.py,
# nothing else from the repo. Same raw-git pattern as build-deploy's
# Checkout step (avoids actions/checkout setup).
run: |
git clone --depth 1 -b ${{ github.head_ref }} https://x-access-token:${PR_TOKEN}@gitea.1000co.fr/${{ github.repository }}.git .
env:
PR_TOKEN: ${{ secrets.PR_API_TOKEN }}
- name: Poll for LGTM
env:
GT_TOKEN: ${{ secrets.PR_API_TOKEN }}
@@ -157,32 +238,54 @@ jobs:
echo "⏳ Attente LGTM sur PR #${PR}..."
LAST_ID=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments" | \
python3 -c "import json,sys; cs=json.load(sys.stdin); print(cs[-1]['id'] if cs else 0)" 2>/dev/null || echo 0)
# Re-fetch ALL comments each cycle (not since_id) so we also catch
# edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for
# PRs with <50 comments.
#
# The LGTM/KO decision is computed by scripts/pr_lgtm_scanner.py
# (a standalone Python file) rather than grep over raw JSON:
# - the bot's "Ready to test" comment body literally contains the
# words LGTM/KO as instructions to the reviewer, so any naive
# body grep would self-match and auto-merge ~30s after deploy.
# We skip it via the <!-- tabatago:ready-to-test --> sentinel
# embedded by the build-deploy job;
# - json.loads avoids false matches when the literal "body" key or
# trigger words appear inside another string field.
# KO is checked BEFORE LGTM so a PR with both signals blocks (matches
# the existing "KO blocks" intent — a reviewer who flip-flops shouldn't
# merge just because an older LGTM is still in the history).
while [ $TRIES -lt $MAX ]; do
sleep 30
TRIES=$((TRIES + 1))
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments?since_id=${LAST_ID}")
"${API}/issues/${PR}/comments?limit=50&page=1")
if echo "$COMMENTS" | grep -qi '"body": *"LGTM"'; then
echo "✅ LGTM reçu ! Merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"merge\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée."
exit 0
fi
# The LGTM/KO scanner lives in scripts/pr_lgtm_scanner.py rather
# than inline here. Embedding multi-line Python in a YAML `run: |`
# block scalar is fragile — YAML dedents block-scalar content to
# the first line, which produced both YAML parse failures and
# Python IndentationErrors in earlier iterations of this workflow.
# A standalone script sidesteps all of that and is testable locally.
DECISION=$(python3 scripts/pr_lgtm_scanner.py "$COMMENTS")
if echo "$COMMENTS" | grep -qi '"body": *"KO"'; then
echo "❌ KO reçu. Bloquée."
exit 1
fi
case "$DECISION" in
LGTM)
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée (squash)."
exit 0
;;
KO)
echo "❌ KO reçu. Bloquée."
exit 1
;;
esac
if [ $((TRIES % 4)) -eq 0 ]; then
echo " ⏳ ... (${TRIES}/240, $(date +%H:%M))"

View File

@@ -152,34 +152,44 @@ Complications : `TabataGoComplication`.
### Workflow : `.github/workflows/pr-iphone-deploy.yml`
**Trigger** : PR ouverte/synchronize/reopened sur `main`.
**Runner** : label `macos` (auto-hébergé).
**Runner** : label `macos` (auto-hébergé). `timeout-minutes: 30` (build-deploy), `120` (wait-approval).
**Concurrency** : `group: pr-iphone-deploy-<PR#>`, `cancel-in-progress: true` → un push sur une PR existante annule le pipeline précédent (pas de double-merge, pas de runner gaspillé). Différentes PRs tournent en parallèle.
**Déroulé** :
1. **Checkout** (shallow, branche head) via `PR_API_TOKEN`.
2. **Setup PATH** : `echo "/opt/homebrew/bin" >> $GITHUB_PATH` — requis car Rosetta ne le voit pas.
3. **Clean ciblé** :
- Supprime build artifacts, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
- **GARDE le cache SPM** (`../build/spm-cache`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox.
4. **Install tools** : `brew install xcodegen node ios-deploy`.
5. **xcodegen generate**`xcodebuild -resolvePackageDependencies`**build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`).
6. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB.
7. **Post comment** "Prêt à tester" sur la PR.
8. **Job `wait-approval`** : poll (30s, max 240 = 2h) les commentaires de la PR. `LGTM` → auto-merge. `KO` → blocage. Timeout → fail.
- Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
- **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`.
- Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants).
4. **Install tools** : `arch -arm64 brew install xcodegen` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. Seul xcodegen est nécessaire : **node n'est pas utilisé** par ce workflow, et l'installer déclenche le "installed dependents check" de brew qui essaie de rebuilder des formulae sans rapport (ex. `gemini-cli`) et fail le build.
5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre change­ment nécessaire côté build.
6. **xcodegen generate**`xcodebuild -resolvePackageDependencies`**build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS).
7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`.
8. **Post comment** "Prêt à tester" sur la PR.
9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Le parsing se fait en **python3** (`json.loads` + regex, pas de `grep` sur JSON brut). **Le commentaire bot "Prêt à tester" porte un marker sentinel `<!-- tabatago:ready-to-test -->`** et est **ignoré** par le scanner — sinon son propre body (qui mentionne LGTM/KO comme instructions au reviewer) déclencherait un auto-merge ~30s après le deploy (bug historique). KO est checké **avant** LGTM (un PR avec les deux signaux bloque). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM`**squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail.
### Secrets
| Secret | Usage |
|--------|-------|
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`** |
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Injectés via `Config/Secrets.xcconfig` → Info.plist |
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`**. Tout passe par l'API Gitea avec ce token — un bloc `permissions:` natif serait no-op, c'est pourquoi il n'y en a pas. |
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Un step CI écrit `Config/Secrets.xcconfig` depuis ces 4 secrets **avant** `xcodegen generate`. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc. ; `project.yml` wire le xcconfig en Debug+Release. `POSTHOG_API_KEY` peut être vide (PostHog pas encore câblé en SPM). |
> ⚠️ **Historique** : `Config/Secrets.xcconfig` a été committé par erreur dans le passé (avant la règle gitignore) avec des valeurs réelles. Le fichier est maintenant gitigné et écrasé par CI, mais l'historique git contient encore l'ancienne version — **rotation des clés Supabase/RevenueCat recommandée côté ops**.
### ⚠️ Pitfalls CI
- `/opt/homebrew/bin` **hors PATH par défaut** sous Rosetta → toujours l'ajouter.
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer).
- **Ne jamais `rm -rf` le cache SPM** dans le CI (c'est l'inverse des build artifacts).
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). Il liste **4 targets** : `TabataGo`, `TabataGoWidget`, `TabataGoWatch`, `TabataGoWatchWidget`.
- **Ne jamais `rm -rf build/` ni `rm -rf build/spm-cache`** dans le CI — RevenueCat y vit. Le clean step ne touche que `build/derived`.
- `SWIFT_ENABLE_EXPLICIT_MODULES=NO` au build — requis sinon segfault linker sur `.pcm` stale.
- `-skipPackagePluginValidation -allowProvisioningUpdates`.
- **Concurrency** : le bloc `concurrency:` est volontaire — ne pas le retirer, sinon les pushes successifs empilent des pipelines et tentent des double-merges.
- **LGTM/KO regex** : matche n'importe où dans le body avec `\b...\b` (word boundary). Ne pas revenir à un `grep '"body": *"LGTM"'` ancré — il raterait "Tested, LGTM!".
- **Self-match sentinel** : le commentaire bot "Prêt à tester" contient littéralement `Reply **LGTM** pour merger` et `Reply **KO**` — sans précaution, le scanner matche son **propre** commentaire et auto-merge ~30s après le deploy. Le body porte donc un marker `<!-- tabatago:ready-to-test -->` que `wait-approval` ignore. **Tout commentaire bot posté par ce workflow doit porter ce marker** ; ne pas le retirer ni poster d'autre commentaire contenant LGTM/KO sans marker.
- **Squash-merge** (`{"Do":"squash"}`) — pas merge commit ni rebase. Garde l'historique `main` linéaire.
- **Comment edits** : `wait-approval` re-fetche tous les comments chaque cycle (pas `since_id`) pour attraper les edits.
---
@@ -219,7 +229,7 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
6. **Navigation = TabView + sheet/fullScreenCover.** **Pas de `NavigationStack`.**
7. **Tout en français** côté user-facing (`Localizable.xcstrings` / `L10n`). Codes/types restent en anglais.
8. **XcodeGen.** Éditer `project.yml`, pas `.xcodeproj`. Régénérer avec `xcodegen generate`.
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`.
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. En CI, un step écrit ce fichier depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`) **avant** `xcodegen generate` ; `Info.plist` lit les valeurs via `$(VAR)` et `project.yml` wire le xcconfig en Debug+Release.
10. **Lazy d'abord.** Avant d'ajouter une dépendance : stdlib Apple, puis ce qui existe déjà dans le repo, puis SPM déjà installé. PostHog n'est pas installé — ne pas l'ajouter sans justification.
---
@@ -234,9 +244,16 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
| `@ObservableObject` / `@Published` | `@Observable` |
| Force-unwrap `!` | `guard let` |
| Strings UI en anglais | Françaises dans `L10n`/`Localizable.xcstrings` |
| `rm -rf` le cache SPM en CI | Garder `build/spm-cache` |
| `rm -rf build/` ou `rm -rf build/spm-cache` en CI | Garder `build/spm-cache` ; ne cleaner que `build/derived` |
| `rm -rf ~/Library/Caches/com.apple.dt.Xcode` | Laisser le cache Xcode global intact (sinon tous les builds ralentissent) |
| Utiliser `GITEA_TOKEN` en CI | `PR_API_TOKEN` uniquement |
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas |
| Compter sur un bloc `permissions:` natif Gitea Actions | Tout passe par l'API Gitea avec `PR_API_TOKEN` ; le bloc natif serait no-op |
| Retirer le bloc `concurrency:` | Permet double-merge et pipelines empilés |
| Ancre le grep LGTM au début du body (`'"body": *"LGTM"'`) | Regex `\bLGTM\b` n'importe où dans le body (attrape "Tested, LGTM!") |
| Poster un commentaire bot dont le body contient "LGTM"/"KO" sans le marker `<!-- tabatago:ready-to-test -->` | Le scanner bot-scan ignore les comments portant le marker — tout commentaire bot du workflow doit l'inclure (sinon auto-merge en ~30s) |
| Scanner les comments en `grep` sur le JSON brut | `python3` + `json.loads` (gère unicode/quotes, pas de false match sur la clé `"body"`) |
| `{"Do":"merge"}` (merge commit) | `{"Do":"squash"}` pour un `main` linéaire |
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas — scheme doit lister `TabataGo`/`TabataGoWidget`/`TabataGoWatch`/`TabataGoWatchWidget` |
| Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement |
| Recréer `programs`/`program_workouts` | Remplacés par `workout_programs`/`program_tabatas` (migration 005) |

View File

@@ -1,6 +1,6 @@
{
"name": "my-app",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""LGTM/KO scanner for the pr-iphone-deploy workflow's wait-approval job.
Reads a Gitea PR comments JSON array (as returned by
``/api/v1/repos/{owner}/{repo}/issues/{pr}/comments``) on argv[1] and prints
exactly one of: ``LGTM``, ``KO``, ``PENDING``.
Rules
-----
* The bot's own "Ready to test" comment body literally contains the words
``LGTM`` and ``KO`` as instructions to the reviewer — without filtering it
out, the workflow would self-merge ~30s after deploy. Bot comments are
tagged with the sentinel ``<!-- tabatago:ready-to-test -->`` and skipped.
* ``KO`` is checked BEFORE ``LGTM`` — a PR with both signals blocks (matches
the existing "KO blocks" intent).
* Word-boundary regex (``\\bLGTM\\b`` / ``\\bKO\\b``, case-insensitive) so
"Tested, LGTM!" counts and "KOM" / "LGTMX" do not.
This script lives in ``scripts/`` (not inline in the workflow) because
embedding multi-line Python inside a YAML ``run: |`` block scalar is fragile:
YAML dedents block-scalar content to the first line, which produces either a
YAML parse failure or a Python ``IndentationError`` depending on how the
body is indented. A standalone file sidesteps all of that.
"""
import json
import re
import sys
MARKER = "<!-- tabatago:ready-to-test -->"
LGTM_RE = re.compile(r"\bLGTM\b", re.IGNORECASE)
KO_RE = re.compile(r"\bKO\b", re.IGNORECASE)
def decide(comments_json: str) -> str:
"""Return 'KO', 'LGTM', or 'PENDING' for the given comments JSON payload."""
try:
comments = json.loads(comments_json or "[]")
except Exception:
comments = []
hit_lgtm = False
hit_ko = False
for comment in comments:
body = comment.get("body") or ""
if MARKER in body:
# Skip the bot's own "Ready to test" comment — its body mentions
# LGTM/KO as instructions and would otherwise self-trigger.
continue
if KO_RE.search(body):
hit_ko = True
if LGTM_RE.search(body):
hit_lgtm = True
if hit_ko:
return "KO"
if hit_lgtm:
return "LGTM"
return "PENDING"
def main() -> int:
if len(sys.argv) != 2:
print("usage: pr_lgtm_scanner.py <comments_json>", file=sys.stderr)
return 2
print(decide(sys.argv[1]))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "full.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,19 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(178.668,1429.023,-572.914,71.631,128.681,37.656)">
<stop offset="0" stop-color="#ff6b35"/>
<stop offset=".275" stop-color="#cc5dd1"/>
<stop offset=".345" stop-color="#a657cd"/>
<stop offset=".503" stop-color="#8050c9"/>
<stop offset=".627" stop-color="#a657cd"/>
<stop offset=".714" stop-color="#cc5dd1"/>
<stop offset="1" stop-color="#ff6b35"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
</style>
<g id="Layer 1">
<path class="s0" d="m149.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68 1.43 16.48 5.24 32.66 11.29 48.06 10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63-7.1-23.21-10.35-47.43-9.61-71.69 1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72zm79.43-519.52c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-16.14 26.38-18.51 52.09l-2.19-0.67c0.76-6.48 0.66-8.78 0.37-15.3-10.91-1.12-22.08 2.71-33 2.71-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-3.77 14.36-7.36 21.63 0.14-13.93 0.79-28.35-4.64-41.58-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23zm-213.96 588.23c1.83 2.25 3.56 3.43 4.04 6.5l-0.15 0.37c0.67-0.12 3.42-0.65 4.11-0.48l0.78 0.28c-0.14 0.74-0.64 1.26-1.26 1.73 11.75 5.35 23.86-1.69 35.49 2.76 2.43 0.93 11.87-0.55 18.16 3.27 0.58 0.35-1.16 1.99-1.07 2.25-10.21 21.3-28.3 54.35-20.7 77.65 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-3.84-17.11-4.32-24.04zm410.44 25.02c2.06-0.34 3.75-0.95 5.81-1.46l-2.41-1c10.21-1.59 18.92 0.05 29.06-0.16 6.49-0.14 14.59-3.22 21.22-4.13l-1.07 5.11c-2.79 19.29-4.66 36.49-4.32 56.19 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-11.35-36.01-19.9-54.48z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "lower.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,33 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(118.66,949.065,-461.315,57.678,134.904,524.495)">
<stop offset="0" stop-color="#262bdf"/>
<stop offset="1" stop-color="#ff004a"/>
</linearGradient>
<linearGradient id="g2" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(115.853,-95.706,134.182,162.429,-1.643,770.379)">
<stop offset="0" stop-color="#010319"/>
<stop offset="1" stop-color="#432640"/>
</linearGradient>
<linearGradient id="g3" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(67.601,0,0,205.585,413.199,632.988)">
<stop offset="0" stop-color="#432640"/>
<stop offset=".618" stop-color="#221320"/>
<stop offset="1" stop-color="#000000"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
.s1 { fill: #432640 }
.s2 { fill: #420a44 }
.s3 { fill: url(#g2) }
.s4 { fill: url(#g3) }
</style>
<g id="Layer 1">
<path class="s0" d="m151.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68 1.43 16.48 5.24 32.66 11.29 48.06 10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63-7.1-23.21-10.35-47.43-9.61-71.69 1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72z"/>
<path class="s1" d="m228.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-15.09 26.03-17.46 51.74l-1.98-0.54c0.76-6.48 0.84-8.58 0.55-15.1-10.91-1.12-23.52 2.73-34.44 2.73-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-2.89 15.84-6.48 23.11 0.14-13.93-0.09-29.83-5.52-43.06-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23z"/>
<path class="s2" d="m137.49 486.15l0.83 0.4c3.58 3.86 4.57 6.2 7.04 10.78 2.53 1.37 29.78-6.88 35.79-7.66 31.97-4.15 57.07-8.91 89.05-5.22 14.5 1.66 73.6-6.97 83.4 4.36 3.86-2.96 2.27-7.93 6.54-10.82l1.66 0.39c-11.83 23.62-11.9 26.19-15.71 50.68l-3.13-1.07c0.76-6.49 0.74-6.53 0.45-13.06-10.93-1.13-22.64 2.8-33.58 2.8-45.39-0.02-91.69-3.51-136.7 3.54-4.31 0.68-14.32-0.91-15.42 3.51-3.59 14.44-3.36 14.33-6.95 21.62 0.14-13.96 0.37-28.35-5.06-41.6-2.2-5.37-5.47-11.9-7.36-17.1zm-125.64 103.79c-0.05-0.1 4.68 12.22 3.52 10.71 0.38 0 7.47-4.2 11.74-1.94 0.09 0.04 7.38 0.95 7.68 1.41 9.61-0.67 19.35-1.03 28.95 0.48 5 0.8 12.28-1.42 16.12 1.4 2.04 1.69 3.07-2.07 4.4 1.27l0.47 0.38c-0.52 0.44 2.95-9.32 5.93-12.26-2.3 9.24-12.2 32.91-16.89 42.03-0.36-0.98-0.86-2.66-0.89-3.58q0.43-0.51 0.85-1.02c-2.02-1.71-14.65-1.9-17.06-2.37-12.24-2.4-23.77 2.59-35.54-2.77 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.93 0.69-4.6 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.92-1.64-3.12-2.88-22.79-3.01-26.9z"/>
<path class="s1" d="m52.76 380.21c1.37 2.56 1.35 8.38 1.48 11.3l-1.27 1.58c-4.76 5.75 0.61 8.2-4.45 13.72-0.83 0.9-0.88 2.54-0.98 3.59-1.84 2.76-0.81 6.37-1.73 8.77-1.8 4.72 1.73 8.02-0.56 11.78-2.34 3.4 1.25 7.72-0.94 10.32q-0.94 0.84-1.89 1.67-0.19 0.17-0.38 0.33c-0.53-17.02 1.6-48.35 10.72-63.06z"/>
<path class="s1" d="m146.83 300c0.83-3.28 2.54-4.24 7.14-4.06 4.96 0.2 3.67-1.38 6.81-1.97 0.4 4.07 5.05 0.81 7.46 2.52 0.78-0.54-11.99 4.75-12.8 5.04-6.77 2.38-12.92 3.93-19.38 7.33-5.8 2.87-2.93 1.63-5.27-0.28-1.44 0.13-1.59 4.98-3.34 3.63-0.26-0.2-2.01-2.29-2.37-2.65 3.82-15.23-2.85 11.41 0 2.23-1.07 3.47-13.54 10.35-17.46 12.26-2.93-2.2 3.96-3.51 4.37-5.03-0.22-0.65-0.83-0.83-0.67-1.57 1.13-1.24 2.95-0.34 3.64-1.95 0.3-0.71-0.28-1.34 0.35-2.22 1.65-0.74 1.43 0.22 3.59-1.15 5.05-4.04 4.65-6.42 9.73-9.97 1.09-0.75 8.74-0.94 8.79-0.92 0.13 0.53 0 1.21 0.24 1.66 2.96 1.13 1.02-5.73 4.18-4.37 0.47 1.14-0.53 2.33-0.17 3.44 3.23-1.43 1.84-1.39 5.16-1.97z"/>
<path class="s3" d="m14.38 614.82c1.83 2.25 3.81 2.64 4.29 5.71l-0.01 0.02c0.67-0.12 3.9-0.98 4.59-0.81l0.19 0.2c-0.14 0.74-0.8 1.04-1.42 1.51 11.75 5.35 23.27 0.37 35.49 2.76 2.4 0.47 15.01 0.66 17.03 2.37q-0.42 0.51-0.85 1.02c0.03 0.92 0.82 2.62 1.18 3.6-10.21 21.3-28.57 52.88-20.97 76.18 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-3.96-15.34-4.44-22.27z"/>
<path class="s4" d="m425 638.26c0.01-0.17-0.43-0.84-0.56-1.11l-0.61-1.1c10.8-2.24 22.84-0.22 32.98-0.43 6.49-0.14 12.95-1.72 19.58-2.63l1.42 1.71c-2.79 19.29-4.92 38.22-4.58 57.92 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-11.29-35.82-19.84-54.29z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "upper.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,35 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(79.084,632.495,-470.376,58.814,180.959,30.752)">
<stop offset="0" stop-color="#ff0031"/>
<stop offset="1" stop-color="#ff9f0a"/>
</linearGradient>
<linearGradient id="g2" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(115.853,-95.706,133.729,161.88,-1.421,770.649)">
<stop offset="0" stop-color="#190a01"/>
<stop offset="1" stop-color="#442a27"/>
</linearGradient>
<linearGradient id="g3" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(135.335,-111.799,103.573,125.376,377.747,785.651)">
<stop offset="0" stop-color="#442a27"/>
<stop offset="1" stop-color="#190a01"/>
</linearGradient>
</defs>
<style>
.s0 { fill: #442a27 }
.s1 { fill: url(#g1) }
.s2 { fill: #b27866 }
.s3 { fill: #fe0128 }
.s4 { fill: #ff9f0a }
.s5 { fill: url(#g2) }
.s6 { fill: url(#g3) }
</style>
<g id="Layer 1">
<path class="s0" d="m149.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68q0.54 6.17 1.52 12.29 0.98 6.12 2.39 12.15 1.42 6.03 3.27 11.94 1.84 5.91 4.11 11.68c10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63q-2.66-8.69-4.6-17.57-1.94-8.87-3.14-17.88-1.21-9.01-1.68-18.08-0.47-9.08-0.19-18.16c1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72z"/>
<path class="s1" d="m229.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-17.01 26.91-19.38 52.62l-1.99-1.31c0.76-6.48 0.72-8.7 0.43-15.22-10.91-1.12-21.47 2.74-32.39 2.74-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-5.57 14.61-9.16 21.88 0.14-13.93 2.59-28.6-2.84-41.83-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23z"/>
<path class="s2" d="m137.42 484.27l1.61 0.27c3.58 3.85 4.56 6.19 7.03 10.76 2.53 1.37 29.73-6.87 35.73-7.65 31.93-4.14 56.99-8.89 88.92-5.21 14.47 1.66 74.45-6.2 84.24 5.11 3.85-2.95 2.1-8.27 6.37-11.16l2.29 0.45c-11.81 23.58-16.74 26.67-19.11 52.38l-2.66-1.06c0.76-6.48 1.14-8.44 0.85-14.96-10.91-1.12-21.49 2.47-32.41 2.47-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-5.64 15-9.23 22.27 0.14-13.93 2.66-28.99-2.76-42.22-2.2-5.36-6.53-11.7-8.42-16.89zm-124.66 103.49c-0.05-0.1 4.67 12.2 3.51 10.69 0.38 0 7.46-4.19 11.72-1.93 0.09 0.04 7.37 0.94 7.67 1.4 9.6-0.67 19.32-1.03 28.91 0.48 4.99 0.8 12.26-1.42 16.09 1.4 2.04 1.69 3.07-2.07 4.39 1.27l0.47 0.38c-0.52 0.44 3.02-6.61 5.99-9.55-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.64-3.12-2.87-22.76-3-26.86z"/>
<path class="s3" d="m229.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9-2.94 1.76-2.44 1.37-5.52-0.39-1.43-0.82-3.91-3.96-5-4.09-1.5 1.08-0.84 2.69-3.47 2.7-1.6 0.16-4.72-1.05-6 0.5-2.05 2.55-4.53 0.56-7.19 2.17-3.3 1.99 1.76 2.77 1.25 4.74-0.29 0.31-9.15 1.4-10.69 4.55-2.58 5.28-14.9-3.85-2.01-6 3.28-0.54 4.16-1.21 2.39-4.21-1.21 0.13-5.75 0.69-6.93 1.04-2.64 0.78-13.46 2.1-15.32 0.13-5.43-5.79-7.22-9.32 2.21-9.37l-0.02-0.19c-2.05-2.36-2.01 1.66-6.96-5.48z"/>
<path class="s4" d="m52.76 380.21c1.37 2.56 1.35 8.38 1.48 11.3l-1.27 1.58c-4.76 5.75 0.61 8.2-4.45 13.72-0.83 0.9-0.88 2.54-0.98 3.59-1.84 2.76-0.81 6.37-1.73 8.77-1.8 4.72 1.73 8.02-0.56 11.78-2.34 3.4 1.25 7.72-0.94 10.32q-0.94 0.84-1.89 1.67-0.19 0.17-0.38 0.33c-0.53-17.02 1.6-48.35 10.72-63.06z"/>
<path class="s4" d="m146.83 300c0.83-3.28 2.54-4.24 7.14-4.06 4.96 0.2 3.67-1.38 6.81-1.97 0.4 4.07 5.05 0.81 7.46 2.52 0.78-0.54-11.99 4.75-12.8 5.04-6.77 2.38-12.92 3.93-19.38 7.33-5.8 2.87-2.93 1.63-5.27-0.28-1.44 0.13-1.59 4.98-3.34 3.63-0.26-0.2-2.01-2.29-2.37-2.65 3.82-15.23-2.85 11.41 0 2.23-1.07 3.47-13.54 10.35-17.46 12.26-2.93-2.2 3.96-3.51 4.37-5.03-0.22-0.65-0.83-0.83-0.67-1.57 1.13-1.24 2.95-0.34 3.64-1.95 0.3-0.71-0.28-1.34 0.35-2.22 1.65-0.74 1.43 0.22 3.59-1.15 5.05-4.04 4.65-6.42 9.73-9.97 1.09-0.75 8.74-0.94 8.79-0.92 0.13 0.53 0 1.21 0.24 1.66 2.96 1.13 1.02-5.73 4.18-4.37 0.47 1.14-0.53 2.33-0.17 3.44 3.23-1.43 1.84-1.39 5.16-1.97z"/>
<path class="s5" d="m15.76 614.62c1.83 2.25 2.43 2.84 2.91 5.91l-0.01 0.02c0.67-0.12 3.9-0.98 4.59-0.81l0.19 0.2c-0.14 0.74-0.8 1.04-1.42 1.51 11.75 5.35 23.27 0.37 35.49 2.76 2.4 0.47 15.01 0.66 17.03 2.37q-0.42 0.51-0.85 1.02c0.03 0.92 0.82 2.62 1.18 3.6-10.21 21.3-28.57 52.88-20.97 76.18 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-2.58-15.54-3.06-22.47z"/>
<path class="s6" d="m426.14 638.12c-0.82-1.16-1.29-1.71-0.49-1.65l4.06-0.66c10.21-1.59 17.16 0.24 27.3 0.03 6.49-0.14 15.56-2.18 22.19-3.09l-0.56 3.13c-2.79 19.29-5.75 37.04-5.41 56.74 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-10.15-35.96-18.7-54.43z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,131 +1,35 @@
import SwiftUI
struct BodySilhouetteShape: Shape {
func path(in rect: CGRect) -> Path {
let w = rect.width
let h = rect.height
let mx = w * 0.5
var p = Path()
let headCY = h * 0.09
let headR = w * 0.12
p.addEllipse(in: CGRect(
x: mx - headR, y: headCY - headR,
width: headR * 2, height: headR * 2
))
let neckW = w * 0.06
let neckTop = headCY + headR
let shoulderY = h * 0.20
p.addRect(CGRect(
x: mx - neckW, y: neckTop,
width: neckW * 2, height: shoulderY - neckTop
))
let shoulderHW = w * 0.42
let armOuterBotY = h * 0.44
let armInnerBotY = h * 0.44
let armOuterX = w * 0.06
let armInnerX = w * 0.12
p.move(to: CGPoint(x: mx - neckW, y: shoulderY))
p.addLine(to: CGPoint(x: mx - shoulderHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx - armOuterX, y: armOuterBotY))
p.addLine(to: CGPoint(x: mx - armInnerX, y: armInnerBotY))
p.closeSubpath()
p.move(to: CGPoint(x: mx + neckW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + shoulderHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + armOuterX, y: armOuterBotY))
p.addLine(to: CGPoint(x: mx + armInnerX, y: armInnerBotY))
p.closeSubpath()
let torsoHW = w * 0.18
let waistY = h * 0.46
let waistHW = w * 0.15
p.move(to: CGPoint(x: mx - torsoHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + torsoHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx - waistHW, y: waistY))
p.closeSubpath()
let hipHW = w * 0.22
let hipY = h * 0.52
p.move(to: CGPoint(x: mx - waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx + waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx + hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx - hipHW, y: hipY))
p.closeSubpath()
let gap = w * 0.02
let legBotY = h * 0.92
let footH = h * 0.05
let footExtra = w * 0.04
p.move(to: CGPoint(x: mx - hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx - gap, y: hipY))
p.addLine(to: CGPoint(x: mx - gap, y: legBotY))
p.addLine(to: CGPoint(x: mx - hipHW - footExtra, y: legBotY + footH))
p.addLine(to: CGPoint(x: mx - hipHW, y: legBotY))
p.closeSubpath()
p.move(to: CGPoint(x: mx + gap, y: hipY))
p.addLine(to: CGPoint(x: mx + hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx + hipHW, y: legBotY))
p.addLine(to: CGPoint(x: mx + hipHW + footExtra, y: legBotY + footH))
p.addLine(to: CGPoint(x: mx + gap, y: legBotY))
p.closeSubpath()
return p
}
}
/// Body-zone highlight icon backed by vector SVG assets in `Assets.xcassets`.
///
/// Each zone renders its own artwork:
/// - `upper-body` `ZoneIconUpper`
/// - `lower-body` `ZoneIconLower`
/// - `full-body` `ZoneIconFull`
///
/// The SVGs ship with their own baked-in colors, so the icon is **not**
/// tinted by `Theme.zoneGradient` it shows the artwork as authored.
/// To make icons auto-recolor per zone instead, switch the imagesets to
/// single-color SVGs and set `"template-rendering-intent": "template"`
/// in each `Contents.json`, then apply `.foregroundStyle(...)` here.
///
/// Drawn on a 56×80 frame with `scaledToFit()` so the SVG keeps its
/// native aspect ratio inside the card.
struct ZoneHighlightIcon: View {
let zone: String
private var waistFraction: CGFloat { 0.50 }
var body: some View {
let shape = BodySilhouetteShape()
ZStack {
shape
.fill(.white.opacity(0.12))
shape
.fill(zoneGradient)
.mask(zoneMask)
}
.frame(width: 56, height: 80)
Image(decorative: imageName, bundle: .main)
.resizable()
.scaledToFit()
.frame(width: 56, height: 80)
}
private var zoneGradient: LinearGradient {
switch zone {
case "upper-body":
LinearGradient(colors: [.orange, .red.opacity(0.8)], startPoint: .top, endPoint: .bottom)
case "lower-body":
LinearGradient(colors: [.blue, .purple.opacity(0.8)], startPoint: .top, endPoint: .bottom)
case "full-body":
LinearGradient(colors: [Theme.brand, .purple], startPoint: .top, endPoint: .bottom)
default:
LinearGradient(colors: [.gray, .secondary], startPoint: .top, endPoint: .bottom)
}
}
@ViewBuilder
private var zoneMask: some View {
switch zone {
case "upper-body":
Rectangle()
.frame(height: 80 * waistFraction)
.frame(maxHeight: .infinity, alignment: .top)
case "lower-body":
Rectangle()
.frame(height: 80 * waistFraction)
.frame(maxHeight: .infinity, alignment: .bottom)
case "full-body":
Rectangle()
default:
Rectangle()
private var imageName: String {
switch zone.lowercased() {
case "upper-body", "upper": return "ZoneIconUpper"
case "lower-body", "lower": return "ZoneIconLower"
default: return "ZoneIconFull"
}
}
}

View File

@@ -216,6 +216,8 @@ schemes:
targets:
TabataGo: all
TabataGoWidget: all
TabataGoWatch: all
TabataGoWatchWidget: all
run:
config: Debug
archive:

View File

@@ -1,3 +1,3 @@
{
"version": "1.0.0"
"version": "1.1.0"
}