Commit Graph

114 Commits

Author SHA1 Message Date
airkjw c78dea71e0 docs(store): remove IAP references (ad-only launch)
Drop the In-App Purchases section from the privacy policy and the
'Remove Ads' bullet from the store description, since the app now ships
ad-supported only. Renumber policy sections; bump policy date to Jun 15.
Redeployed the policy to block-seasons.web.app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:10:36 +09:00
airkjw 8b5bbd9531 feat: ship ad-supported only — gate Remove Ads IAP behind kIapEnabled=false
The developer is an individual without a Korean business registration, so
the App Store / Play paid-apps (merchant) agreements can't be completed.
Hide the Remove Ads + Restore tiles and skip IAP init; ads always show.
AdMob revenue is independent of those agreements. Reversible: flip
kIapEnabled to re-enable once a merchant agreement exists. Bump to build 2;
drop the now-unused IAP review-screenshot generator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:55:48 +09:00
airkjw e9d7f7cef6 test(store): match IAP review screenshot price to $1.99 (actual price) 2026-06-15 10:48:51 +09:00
airkjw 3e136dc288 test(store): generate IAP review screenshot for App Store (remove_ads)
Headless render of the Settings screen showing the Remove Ads purchase
point + price + Restore Purchases, for the App Store IAP review-screenshot
requirement. Forces a desktop target platform so the real IapService can
be constructed without the store plugin making async channel calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:46:54 +09:00
airkjw ea01da9b62 ios: declare ITSAppUsesNonExemptEncryption=false (HTTPS-only, export-exempt)
App uses only standard OS-provided encryption (HTTPS via Firebase/AdMob),
which qualifies for export compliance exemption. Baking the key into
Info.plist avoids the per-build encryption prompt on future uploads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:21:45 +09:00
airkjw 8e3ed2951d docs(store): add real landing page for web.app root (support/marketing URL)
Replaces the default Firebase placeholder so the App Store support and
marketing URLs (https://block-seasons.web.app) show a real Block Seasons
page. Deployed to Firebase Hosting root as index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:11:16 +09:00
airkjw b79960c949 docs(store): host privacy policy + app-ads.txt on Firebase, fill submission URLs
Deployed privacy-policy.html and app-ads.txt to block-seasons.web.app
(gru.farm root is an external site builder, not the NAS, so app-ads.txt
could not live there). Updated phase7 guide with the live URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:55:21 +09:00
airkjw 08372995bc docs(store): generate store screenshots (iOS 6.7" + Android phone)
Headless screenshot harness renders Home, Gameplay, and a denser
score-chase board to PNGs at 1290x2796 (iOS) and 1080x1920 (Android),
in English with a real font loaded. Captures the boundary layer at 3x
inside runAsync (the only way it completes under the test binding).
Season-map cut deferred — its scrollable build stalls headlessly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:20:37 +09:00
airkjw 30572e3912 docs(store): self-serve submission guide + privacy policy + Play 512 icon
Rewrote the Phase 7 guide to reflect completed Play setup (app, AAB,
payments profile, remove_ads) and lay out remaining ordered steps for
both stores with copy/answers inline. Added a hostable bilingual
privacy policy page and a 512x512 Play store icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:49:41 +09:00
airkjw c7c558cb96 release: drop test-only build tag from settings footer
The '(build 4)' marker was a device build-delivery debugging aid, now
resolved. Public release shows a clean version string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:47:33 +09:00
airkjw 7c7c7afad0 content: regenerate Season 1 with a gentle onboarding difficulty curve
Generator: raise the early bot-win-rate floor to ~0.97 (near-guaranteed first
stages), widen the early move-buffer margin, and delay score-chase/line-sprint
objectives until stage 9/13 so the opening stretch is pure clear-a-gem. Result:
stage 1 now 16 moves (was 11), stages 1-9 all simple gems at 90-99% bot win.
Preserves theme.bgm=season_001; manifest SHA regenerated. Settings tag -> build 4.
2026-06-14 11:02:29 +09:00
airkjw 1682578501 feat: start at Season 1, add menu/season_001 BGM tracks, settings build tag
- activeSeason now returns the first season (Season 1 'First Bloom') so a new
  player starts at spring instead of the newest season.
- Bundle the owner-picked CC0 tracks menu.mp3 + season_001.mp3 (BGM now audible).
- Settings footer shows 'v1.0.0 (build 3)' so test builds are identifiable.
180 tests green, analyze clean.
2026-06-14 10:32:12 +09:00
airkjw 8947221b27 feat(audio): looping per-season background music system
MusicService (looping audioplayers player, independent of SFX) driven by the
active season theme's new 'bgm' key; switches track on season change, pauses on
app background, all failures swallowed. Separate Music on/off toggle in Settings
(persisted, independent of SFX). Season packs carry bgm keys (menu/season_001/
season_002), manifest regenerated. Assets slot assets/audio/bgm/ ready — drop in
menu.mp3/season_001.mp3/season_002.mp3 (CC0) and it plays; silent until then.
180 tests green, analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 09:31:10 +09:00
airkjw 2310aabdb9 fix(ui): move game-screen close button to its own top row (was overlapping HUD)
On device the floating top-left X (48px IconButton) overlapped the HUD moves
chip. Move it into the column flow above the HUD, left-aligned, so it never
collides. Found via on-device test build.
2026-06-14 09:02:01 +09:00
airkjw a9380a7b27 fix(android): launcher label 'Block Seasons' (was default 'block_seasons') 2026-06-13 23:14:12 +09:00
airkjw 40abc26f5d build(release): Android signing, iOS privacy manifest, store assets (Phase 7)
- Android: release keystore signing wired via gitignored key.properties (falls
  back to debug when absent). Verified: signed AAB built (signer CN=Block Seasons).
- iOS: app PrivacyInfo.xcprivacy (ATT tracking flag, device-id/usage data types,
  UserDefaults+FileTimestamp required-reason APIs) registered in the Runner target.
- Store: app-ads.txt (pub-5605900229781491), EN/KO listing copy, owner submission
  guide (privacy labels, app-ads hosting, upload/submit steps).
Secrets (keystore, key.properties) are gitignored — owner backs them up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:49:43 +09:00
airkjw 734c8a4cf7 feat(ads): wire real AdMob ids (Phase 5 finalize)
Replaces the TODO_REAL_* ad-unit placeholders with the owner's real AdMob
console ids (publisher pub-5605900229781491) and the native AdMob app ids
(iOS ~8397095848, Android ~8257495040) in place of the Google sample ids.
Debug builds still serve Google test ads via kDebugMode; release builds now
use the real units.
2026-06-13 19:05:10 +09:00
airkjw b31228d987 docs: AdMob + IAP real-id setup guide for owner (Phase 5 finalize)
Step-by-step owner guide to register the AdMob apps + 6 ad units, create the
remove_ads IAP in both stores, and the exact values to send back so the
TODO_REAL_* ad ids and native AdMob app ids get swapped in.
2026-06-13 18:31:20 +09:00
airkjw 219da8677a Merge Phase 6: localization finalize + brand icon + juice + store assets
Brand app icon (navy 2x2 glossy block mark, vector-drawn, no AI image) applied
on iOS+Android via flutter_launcher_icons. Sound & vibration toggle (themed
Settings). Juice: button press feedback + fade screen transitions. Play feature
graphic (Titan One wordmark). l10n EN/KO verified (no hardcoded strings, key
parity). 176 tests green; analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 18:26:48 +09:00
airkjw ac49168c02 test(juice): PressableScale doesn't swallow the inner button tap 2026-06-13 18:26:20 +09:00
airkjw 23f90d5b89 style: drop unnecessary dart:ui import in app_icon_painter 2026-06-13 18:25:13 +09:00
airkjw bc62127d1a docs: Phase 6 verified — brand app icon applied on device build 2026-06-13 18:24:36 +09:00
airkjw b0839aba2a fix(store): render feature-graphic wordmark with real font (Titan One, OFL)
The flutter_test default font draws every glyph as a box, so the wordmark was
unreadable. Load the OFL Titan One TTF via FontLoader in the generator and
render 'Block Seasons' + tagline with it; fit the text within the 1024 width.
Font used only by the asset generator, not bundled in the app.
2026-06-13 18:20:11 +09:00
airkjw 7fe2bc2776 feat(store): Play feature graphic (1024x500) generator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:15:11 +09:00
airkjw 536807e7c8 feat(juice): button press feedback + fade screen transitions
Adds PressableScale (0.94 squish on tap-down) around the Adventure
FilledButton, Classic OutlinedButton, and each season-map stage node.
Replaces all in-app MaterialPageRoute pushes with a gentle fade+scale
PageRouteBuilder (320ms in, 240ms out) via a new fadeRoute helper.
2026-06-13 18:12:07 +09:00
airkjw e1098949be build(brand): generate iOS/Android launcher icons from brand mark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:08:13 +09:00
airkjw 099ced377d feat(brand): app icon painter + generated 1024px icon PNGs
Introduces AppIconMark (navy gradient field + 2×2 glossy blocks reusing
paintGlossyTile) and a testWidgets generator that writes icon.png,
icon_background.png, and icon_foreground.png to assets/icon/ for the
upcoming flutter_launcher_icons task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:06:18 +09:00
airkjw 498fb6af83 feat(settings): sound & vibration toggle; themed settings screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:01:23 +09:00
airkjw 3ca038ec65 feat(settings): soundEnabled provider gates SFX and haptics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:59:08 +09:00
airkjw 93397988a2 feat(settings): persist soundEnabled flag (additive, default true)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:56:22 +09:00
airkjw ea42c76f84 docs: Phase 6 implementation plan (icon, sound toggle, juice, l10n, store assets)
9 tasks. Icon + feature graphic drawn via CustomPainter and rasterized to PNG
under flutter test (no SVG tooling), consumed by flutter_launcher_icons. Sound
& vibration toggle follows the repo-backed Notifier pattern. Juice: press
feedback + fade routes + themed settings. Tasks 1-6 subagent-friendly; 7-9
controller-run (KO overflow, screenshots, icon-on-device).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:11:45 +09:00
airkjw 84a6749b5e docs: Phase 6 design spec (icon + l10n finalize + juice + store assets)
App icon = clean 2x2 glossy block mark (vector, no AI image). l10n EN/KO
finalize + KO overflow pass. Light juice: sound/haptics toggle, themed
settings, button press feedback, screen transitions. Store feature graphic +
EN/KO screenshots. Procedural visuals mean no raster background assets needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:06:39 +09:00
airkjw 6a0b543970 Merge Phase 5: monetization (AdMob + IAP)
AdMob ads (interstitial/rewarded/banner) with a pure-Dart frequency policy,
a compliant UMP->ATT->init consent flow, and a remove_ads non-consumable IAP
with Restore. Single repo-backed ownership source (adsRemovedProvider); all
ad/IAP/consent failures swallowed. Runs on Google test ids today; owner swaps
real ids by config. 169 tests green; opus final review passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:43:46 +09:00
airkjw c5c6af0313 docs: Phase 5 UMP consent flow verified on simulator (evidence)
First-launch UMP consent form displays, ATT requested, MobileAds SDK
initialized, no crash. Settings gear present, home renders. Banner visual +
sandbox IAP are owner device-test items (consent form blocks automated taps).
2026-06-13 14:29:52 +09:00
airkjw 7bea9c1456 fix(iap): eagerly start IAP service at launch (final review I-1)
iapServiceProvider was lazy and only read by Settings, so the purchase stream
attached only after opening Settings — deferred/interrupted/restored
transactions wouldn't be delivered or completed until then. Read it in the
post-frame callback so the stream is live for the whole session.
2026-06-13 14:29:20 +09:00
airkjw 640b23804f fix(ads): banner retries load when SDK becomes ready
On a cold start the consent flow is still running when home first builds, so
createBanner returns null and the slot stayed empty until a rebuild. AdService
now exposes an isReady ValueNotifier; BannerAdSlot listens and retries its load
once MobileAds finishes initializing. Verified: analyze clean, 169 tests green.
2026-06-13 14:23:45 +09:00
airkjw 70f87ab8f2 feat(iap): settings screen with remove-ads purchase and restore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:17:28 +09:00
airkjw 40c2204d7b feat(ads): home/map banner slot (hidden when removed or unloaded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:13:29 +09:00
airkjw 1ec59ba80d fix(ads): reset ad round in startStage so nextStage advance counts (review fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:09:02 +09:00
airkjw 297449ccce feat(ads): stage-end interstitial gated by policy; restart resets round 2026-06-13 14:03:52 +09:00
airkjw 3943653a23 feat(ads): rewarded ad gates the continue/extra-moves rescue 2026-06-13 14:02:31 +09:00
airkjw 6c4304cfd8 style(test): drop unused import in ads_notifier_test 2026-06-13 14:00:03 +09:00
airkjw 662ee55e1d feat(ads): run consent flow (UMP->ATT->init) after the first frame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:59:22 +09:00
airkjw 539afd1dad feat(ads): ref-constructed ad/consent/iap providers, single ownership source 2026-06-13 13:57:30 +09:00
airkjw 6d2ffebb92 feat(iap): remove_ads purchase/restore service + adsRemoved notifier 2026-06-13 13:55:44 +09:00
airkjw e43fda8551 feat(ads): ConsentService enforcing UMP -> ATT -> SDK init order 2026-06-13 13:53:10 +09:00
airkjw 4744aa167a feat(ads): AdService for interstitial/rewarded/banner with policy gating 2026-06-13 13:49:18 +09:00
airkjw eb258c7324 feat(ads): ad id config with test/real switch via dart-define 2026-06-13 13:46:44 +09:00
airkjw 947d5566a2 feat(iap): persist adsRemoved flag (additive, saveVersion 1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:45:33 +09:00
airkjw f560b9d4c8 feat(ads): pure-Dart interstitial frequency policy with tests 2026-06-13 13:42:55 +09:00