designmd-forge 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/__tests__/designStore.test.js +41 -0
- package/dist/__tests__/linter.test.js +23 -0
- package/dist/__tests__/sectionParser.test.js +60 -0
- package/dist/__tests__/server.test.js +129 -0
- package/dist/constants.js +12 -0
- package/dist/data/authoring-guide.md +113 -0
- package/dist/data/designs/airbnb.md +545 -0
- package/dist/data/designs/airtable.md +554 -0
- package/dist/data/designs/apple.md +562 -0
- package/dist/data/designs/binance.md +634 -0
- package/dist/data/designs/bmw-m.md +503 -0
- package/dist/data/designs/bmw.md +544 -0
- package/dist/data/designs/bugatti.md +454 -0
- package/dist/data/designs/cal.md +542 -0
- package/dist/data/designs/card-heavy.md +250 -0
- package/dist/data/designs/claude.md +589 -0
- package/dist/data/designs/clay.md +541 -0
- package/dist/data/designs/clickhouse.md +544 -0
- package/dist/data/designs/cohere.md +451 -0
- package/dist/data/designs/coinbase.md +570 -0
- package/dist/data/designs/composio.md +506 -0
- package/dist/data/designs/content-first.md +236 -0
- package/dist/data/designs/cursor.md +537 -0
- package/dist/data/designs/data-dashboard.md +300 -0
- package/dist/data/designs/dell-1996.md +632 -0
- package/dist/data/designs/elevenlabs.md +504 -0
- package/dist/data/designs/enterprise-neutral.md +252 -0
- package/dist/data/designs/expo.md +526 -0
- package/dist/data/designs/ferrari.md +531 -0
- package/dist/data/designs/figma.md +578 -0
- package/dist/data/designs/framer.md +544 -0
- package/dist/data/designs/hashicorp.md +575 -0
- package/dist/data/designs/hp.md +670 -0
- package/dist/data/designs/ibm.md +550 -0
- package/dist/data/designs/intercom.md +546 -0
- package/dist/data/designs/ios-acorns-android.md +430 -0
- package/dist/data/designs/ios-acorns-expo.md +381 -0
- package/dist/data/designs/ios-acorns-swiftui.md +378 -0
- package/dist/data/designs/ios-acorns.md +384 -0
- package/dist/data/designs/ios-airbnb-android.md +658 -0
- package/dist/data/designs/ios-airbnb-expo.md +457 -0
- package/dist/data/designs/ios-airbnb-swiftui.md +464 -0
- package/dist/data/designs/ios-airbnb.md +410 -0
- package/dist/data/designs/ios-alltrails-android.md +451 -0
- package/dist/data/designs/ios-alltrails-expo.md +400 -0
- package/dist/data/designs/ios-alltrails-swiftui.md +343 -0
- package/dist/data/designs/ios-alltrails.md +344 -0
- package/dist/data/designs/ios-amazon-android.md +686 -0
- package/dist/data/designs/ios-amazon-expo.md +433 -0
- package/dist/data/designs/ios-amazon-music-android.md +416 -0
- package/dist/data/designs/ios-amazon-music-expo.md +368 -0
- package/dist/data/designs/ios-amazon-music-swiftui.md +436 -0
- package/dist/data/designs/ios-amazon-music.md +389 -0
- package/dist/data/designs/ios-amazon-swiftui.md +460 -0
- package/dist/data/designs/ios-amazon.md +440 -0
- package/dist/data/designs/ios-apple-fitness-android.md +462 -0
- package/dist/data/designs/ios-apple-fitness-expo.md +373 -0
- package/dist/data/designs/ios-apple-fitness-swiftui.md +373 -0
- package/dist/data/designs/ios-apple-fitness.md +399 -0
- package/dist/data/designs/ios-apple-maps-android.md +646 -0
- package/dist/data/designs/ios-apple-maps-expo.md +557 -0
- package/dist/data/designs/ios-apple-maps-swiftui.md +588 -0
- package/dist/data/designs/ios-apple-maps.md +447 -0
- package/dist/data/designs/ios-apple-music-android.md +737 -0
- package/dist/data/designs/ios-apple-music-expo.md +596 -0
- package/dist/data/designs/ios-apple-music-swiftui.md +578 -0
- package/dist/data/designs/ios-apple-music.md +494 -0
- package/dist/data/designs/ios-apple-notes-android.md +675 -0
- package/dist/data/designs/ios-apple-notes-expo.md +468 -0
- package/dist/data/designs/ios-apple-notes-swiftui.md +489 -0
- package/dist/data/designs/ios-apple-notes.md +413 -0
- package/dist/data/designs/ios-apple-tv-android.md +405 -0
- package/dist/data/designs/ios-apple-tv-expo.md +341 -0
- package/dist/data/designs/ios-apple-tv-swiftui.md +348 -0
- package/dist/data/designs/ios-apple-tv.md +390 -0
- package/dist/data/designs/ios-apple-wallet-android.md +617 -0
- package/dist/data/designs/ios-apple-wallet-expo.md +487 -0
- package/dist/data/designs/ios-apple-wallet-swiftui.md +425 -0
- package/dist/data/designs/ios-apple-wallet.md +360 -0
- package/dist/data/designs/ios-asana-android.md +435 -0
- package/dist/data/designs/ios-asana-expo.md +411 -0
- package/dist/data/designs/ios-asana-swiftui.md +416 -0
- package/dist/data/designs/ios-asana.md +433 -0
- package/dist/data/designs/ios-audible-android.md +450 -0
- package/dist/data/designs/ios-audible-expo.md +328 -0
- package/dist/data/designs/ios-audible-swiftui.md +418 -0
- package/dist/data/designs/ios-audible.md +364 -0
- package/dist/data/designs/ios-bandcamp-android.md +471 -0
- package/dist/data/designs/ios-bandcamp-expo.md +378 -0
- package/dist/data/designs/ios-bandcamp-swiftui.md +374 -0
- package/dist/data/designs/ios-bandcamp.md +372 -0
- package/dist/data/designs/ios-bear-android.md +496 -0
- package/dist/data/designs/ios-bear-expo.md +449 -0
- package/dist/data/designs/ios-bear-swiftui.md +479 -0
- package/dist/data/designs/ios-bear.md +412 -0
- package/dist/data/designs/ios-bereal-android.md +392 -0
- package/dist/data/designs/ios-bereal-expo.md +299 -0
- package/dist/data/designs/ios-bereal-swiftui.md +319 -0
- package/dist/data/designs/ios-bereal.md +336 -0
- package/dist/data/designs/ios-binance-android.md +481 -0
- package/dist/data/designs/ios-binance-expo.md +404 -0
- package/dist/data/designs/ios-binance-swiftui.md +418 -0
- package/dist/data/designs/ios-binance.md +410 -0
- package/dist/data/designs/ios-bluesky-android.md +442 -0
- package/dist/data/designs/ios-bluesky-expo.md +362 -0
- package/dist/data/designs/ios-bluesky-swiftui.md +328 -0
- package/dist/data/designs/ios-bluesky.md +338 -0
- package/dist/data/designs/ios-booking-android.md +471 -0
- package/dist/data/designs/ios-booking-expo.md +364 -0
- package/dist/data/designs/ios-booking-swiftui.md +377 -0
- package/dist/data/designs/ios-booking.md +346 -0
- package/dist/data/designs/ios-bumble-android.md +756 -0
- package/dist/data/designs/ios-bumble-expo.md +629 -0
- package/dist/data/designs/ios-bumble-swiftui.md +546 -0
- package/dist/data/designs/ios-bumble.md +424 -0
- package/dist/data/designs/ios-cal-ai-android.md +664 -0
- package/dist/data/designs/ios-cal-ai-expo.md +415 -0
- package/dist/data/designs/ios-cal-ai-swiftui.md +486 -0
- package/dist/data/designs/ios-cal-ai.md +387 -0
- package/dist/data/designs/ios-calm-android.md +431 -0
- package/dist/data/designs/ios-calm-expo.md +384 -0
- package/dist/data/designs/ios-calm-swiftui.md +327 -0
- package/dist/data/designs/ios-calm.md +356 -0
- package/dist/data/designs/ios-canva-android.md +469 -0
- package/dist/data/designs/ios-canva-expo.md +374 -0
- package/dist/data/designs/ios-canva-swiftui.md +422 -0
- package/dist/data/designs/ios-canva.md +395 -0
- package/dist/data/designs/ios-cash-app-android.md +601 -0
- package/dist/data/designs/ios-cash-app-expo.md +453 -0
- package/dist/data/designs/ios-cash-app-swiftui.md +463 -0
- package/dist/data/designs/ios-cash-app.md +365 -0
- package/dist/data/designs/ios-character-ai-android.md +489 -0
- package/dist/data/designs/ios-character-ai-expo.md +420 -0
- package/dist/data/designs/ios-character-ai-swiftui.md +417 -0
- package/dist/data/designs/ios-character-ai.md +392 -0
- package/dist/data/designs/ios-charles-schwab-android.md +454 -0
- package/dist/data/designs/ios-charles-schwab-expo.md +388 -0
- package/dist/data/designs/ios-charles-schwab-swiftui.md +394 -0
- package/dist/data/designs/ios-charles-schwab.md +380 -0
- package/dist/data/designs/ios-chatgpt-android.md +753 -0
- package/dist/data/designs/ios-chatgpt-expo.md +644 -0
- package/dist/data/designs/ios-chatgpt-swiftui.md +653 -0
- package/dist/data/designs/ios-chatgpt.md +466 -0
- package/dist/data/designs/ios-chime-android.md +438 -0
- package/dist/data/designs/ios-chime-expo.md +419 -0
- package/dist/data/designs/ios-chime-swiftui.md +383 -0
- package/dist/data/designs/ios-chime.md +389 -0
- package/dist/data/designs/ios-chipotle-android.md +397 -0
- package/dist/data/designs/ios-chipotle-expo.md +301 -0
- package/dist/data/designs/ios-chipotle-swiftui.md +345 -0
- package/dist/data/designs/ios-chipotle.md +340 -0
- package/dist/data/designs/ios-citymapper-android.md +466 -0
- package/dist/data/designs/ios-citymapper-expo.md +420 -0
- package/dist/data/designs/ios-citymapper-swiftui.md +424 -0
- package/dist/data/designs/ios-citymapper.md +394 -0
- package/dist/data/designs/ios-claude-android.md +625 -0
- package/dist/data/designs/ios-claude-expo.md +671 -0
- package/dist/data/designs/ios-claude-swiftui.md +649 -0
- package/dist/data/designs/ios-claude.md +447 -0
- package/dist/data/designs/ios-clickup-android.md +484 -0
- package/dist/data/designs/ios-clickup-expo.md +423 -0
- package/dist/data/designs/ios-clickup-swiftui.md +416 -0
- package/dist/data/designs/ios-clickup.md +431 -0
- package/dist/data/designs/ios-clubhouse-android.md +524 -0
- package/dist/data/designs/ios-clubhouse-expo.md +425 -0
- package/dist/data/designs/ios-clubhouse-swiftui.md +447 -0
- package/dist/data/designs/ios-clubhouse.md +406 -0
- package/dist/data/designs/ios-coffee-meets-bagel-android.md +461 -0
- package/dist/data/designs/ios-coffee-meets-bagel-expo.md +372 -0
- package/dist/data/designs/ios-coffee-meets-bagel-swiftui.md +368 -0
- package/dist/data/designs/ios-coffee-meets-bagel.md +376 -0
- package/dist/data/designs/ios-coinbase-android.md +611 -0
- package/dist/data/designs/ios-coinbase-expo.md +500 -0
- package/dist/data/designs/ios-coinbase-swiftui.md +490 -0
- package/dist/data/designs/ios-coinbase.md +409 -0
- package/dist/data/designs/ios-copilot-android.md +625 -0
- package/dist/data/designs/ios-copilot-expo.md +423 -0
- package/dist/data/designs/ios-copilot-swiftui.md +471 -0
- package/dist/data/designs/ios-copilot.md +367 -0
- package/dist/data/designs/ios-craft-android.md +459 -0
- package/dist/data/designs/ios-craft-expo.md +417 -0
- package/dist/data/designs/ios-craft-swiftui.md +449 -0
- package/dist/data/designs/ios-craft.md +449 -0
- package/dist/data/designs/ios-crunchyroll-android.md +432 -0
- package/dist/data/designs/ios-crunchyroll-expo.md +388 -0
- package/dist/data/designs/ios-crunchyroll-swiftui.md +417 -0
- package/dist/data/designs/ios-crunchyroll.md +388 -0
- package/dist/data/designs/ios-crypto-com-android.md +461 -0
- package/dist/data/designs/ios-crypto-com-expo.md +404 -0
- package/dist/data/designs/ios-crypto-com-swiftui.md +419 -0
- package/dist/data/designs/ios-crypto-com.md +410 -0
- package/dist/data/designs/ios-deepseek-android.md +471 -0
- package/dist/data/designs/ios-deepseek-expo.md +391 -0
- package/dist/data/designs/ios-deepseek-swiftui.md +354 -0
- package/dist/data/designs/ios-deepseek.md +393 -0
- package/dist/data/designs/ios-deezer-android.md +475 -0
- package/dist/data/designs/ios-deezer-expo.md +400 -0
- package/dist/data/designs/ios-deezer-swiftui.md +430 -0
- package/dist/data/designs/ios-deezer.md +388 -0
- package/dist/data/designs/ios-deliveroo-android.md +493 -0
- package/dist/data/designs/ios-deliveroo-expo.md +424 -0
- package/dist/data/designs/ios-deliveroo-swiftui.md +398 -0
- package/dist/data/designs/ios-deliveroo.md +420 -0
- package/dist/data/designs/ios-delta-android.md +439 -0
- package/dist/data/designs/ios-delta-expo.md +381 -0
- package/dist/data/designs/ios-delta-swiftui.md +400 -0
- package/dist/data/designs/ios-delta.md +397 -0
- package/dist/data/designs/ios-discord-android.md +611 -0
- package/dist/data/designs/ios-discord-expo.md +618 -0
- package/dist/data/designs/ios-discord-swiftui.md +630 -0
- package/dist/data/designs/ios-discord.md +451 -0
- package/dist/data/designs/ios-disney-plus-android.md +464 -0
- package/dist/data/designs/ios-disney-plus-expo.md +358 -0
- package/dist/data/designs/ios-disney-plus-swiftui.md +412 -0
- package/dist/data/designs/ios-disney-plus.md +355 -0
- package/dist/data/designs/ios-dominos-android.md +417 -0
- package/dist/data/designs/ios-dominos-expo.md +316 -0
- package/dist/data/designs/ios-dominos-swiftui.md +340 -0
- package/dist/data/designs/ios-dominos.md +347 -0
- package/dist/data/designs/ios-doordash-android.md +527 -0
- package/dist/data/designs/ios-doordash-expo.md +323 -0
- package/dist/data/designs/ios-doordash-swiftui.md +301 -0
- package/dist/data/designs/ios-doordash.md +393 -0
- package/dist/data/designs/ios-dropbox-android.md +522 -0
- package/dist/data/designs/ios-dropbox-expo.md +393 -0
- package/dist/data/designs/ios-dropbox-swiftui.md +400 -0
- package/dist/data/designs/ios-dropbox.md +374 -0
- package/dist/data/designs/ios-duolingo-android.md +641 -0
- package/dist/data/designs/ios-duolingo-expo.md +597 -0
- package/dist/data/designs/ios-duolingo-swiftui.md +539 -0
- package/dist/data/designs/ios-duolingo.md +409 -0
- package/dist/data/designs/ios-ebay-android.md +436 -0
- package/dist/data/designs/ios-ebay-expo.md +344 -0
- package/dist/data/designs/ios-ebay-swiftui.md +308 -0
- package/dist/data/designs/ios-ebay.md +355 -0
- package/dist/data/designs/ios-espn-android.md +468 -0
- package/dist/data/designs/ios-espn-expo.md +387 -0
- package/dist/data/designs/ios-espn-swiftui.md +410 -0
- package/dist/data/designs/ios-espn.md +384 -0
- package/dist/data/designs/ios-etsy-android.md +439 -0
- package/dist/data/designs/ios-etsy-expo.md +351 -0
- package/dist/data/designs/ios-etsy-swiftui.md +296 -0
- package/dist/data/designs/ios-etsy.md +355 -0
- package/dist/data/designs/ios-evernote-android.md +419 -0
- package/dist/data/designs/ios-evernote-expo.md +379 -0
- package/dist/data/designs/ios-evernote-swiftui.md +380 -0
- package/dist/data/designs/ios-evernote.md +416 -0
- package/dist/data/designs/ios-expedia-android.md +451 -0
- package/dist/data/designs/ios-expedia-expo.md +433 -0
- package/dist/data/designs/ios-expedia-swiftui.md +399 -0
- package/dist/data/designs/ios-expedia.md +431 -0
- package/dist/data/designs/ios-facebook-android.md +628 -0
- package/dist/data/designs/ios-facebook-expo.md +471 -0
- package/dist/data/designs/ios-facebook-swiftui.md +459 -0
- package/dist/data/designs/ios-facebook.md +452 -0
- package/dist/data/designs/ios-feeld-android.md +431 -0
- package/dist/data/designs/ios-feeld-expo.md +379 -0
- package/dist/data/designs/ios-feeld-swiftui.md +401 -0
- package/dist/data/designs/ios-feeld.md +378 -0
- package/dist/data/designs/ios-fidelity-android.md +426 -0
- package/dist/data/designs/ios-fidelity-expo.md +396 -0
- package/dist/data/designs/ios-fidelity-swiftui.md +398 -0
- package/dist/data/designs/ios-fidelity.md +387 -0
- package/dist/data/designs/ios-figma-android.md +578 -0
- package/dist/data/designs/ios-figma-expo.md +504 -0
- package/dist/data/designs/ios-figma-swiftui.md +542 -0
- package/dist/data/designs/ios-figma.md +404 -0
- package/dist/data/designs/ios-fitbit-android.md +439 -0
- package/dist/data/designs/ios-fitbit-expo.md +389 -0
- package/dist/data/designs/ios-fitbit-swiftui.md +380 -0
- package/dist/data/designs/ios-fitbit.md +396 -0
- package/dist/data/designs/ios-flickr-android.md +506 -0
- package/dist/data/designs/ios-flickr-expo.md +434 -0
- package/dist/data/designs/ios-flickr-swiftui.md +421 -0
- package/dist/data/designs/ios-flickr.md +394 -0
- package/dist/data/designs/ios-flighty-android.md +418 -0
- package/dist/data/designs/ios-flighty-expo.md +353 -0
- package/dist/data/designs/ios-flighty-swiftui.md +337 -0
- package/dist/data/designs/ios-flighty.md +357 -0
- package/dist/data/designs/ios-flo-android.md +447 -0
- package/dist/data/designs/ios-flo-expo.md +363 -0
- package/dist/data/designs/ios-flo-swiftui.md +355 -0
- package/dist/data/designs/ios-flo.md +351 -0
- package/dist/data/designs/ios-garmin-connect-android.md +531 -0
- package/dist/data/designs/ios-garmin-connect-expo.md +429 -0
- package/dist/data/designs/ios-garmin-connect-swiftui.md +459 -0
- package/dist/data/designs/ios-garmin-connect.md +419 -0
- package/dist/data/designs/ios-gas-android.md +438 -0
- package/dist/data/designs/ios-gas-expo.md +350 -0
- package/dist/data/designs/ios-gas-swiftui.md +329 -0
- package/dist/data/designs/ios-gas.md +387 -0
- package/dist/data/designs/ios-gemini-android.md +568 -0
- package/dist/data/designs/ios-gemini-expo.md +416 -0
- package/dist/data/designs/ios-gemini-swiftui.md +424 -0
- package/dist/data/designs/ios-gemini.md +351 -0
- package/dist/data/designs/ios-github-android.md +507 -0
- package/dist/data/designs/ios-github-expo.md +486 -0
- package/dist/data/designs/ios-github-swiftui.md +459 -0
- package/dist/data/designs/ios-github.md +394 -0
- package/dist/data/designs/ios-gmail-android.md +548 -0
- package/dist/data/designs/ios-gmail-expo.md +457 -0
- package/dist/data/designs/ios-gmail-swiftui.md +470 -0
- package/dist/data/designs/ios-gmail.md +441 -0
- package/dist/data/designs/ios-goodreads-android.md +436 -0
- package/dist/data/designs/ios-goodreads-expo.md +407 -0
- package/dist/data/designs/ios-goodreads-swiftui.md +378 -0
- package/dist/data/designs/ios-goodreads.md +385 -0
- package/dist/data/designs/ios-google-calendar-android.md +565 -0
- package/dist/data/designs/ios-google-calendar-expo.md +616 -0
- package/dist/data/designs/ios-google-calendar-swiftui.md +585 -0
- package/dist/data/designs/ios-google-calendar.md +431 -0
- package/dist/data/designs/ios-google-maps-android.md +614 -0
- package/dist/data/designs/ios-google-maps-expo.md +515 -0
- package/dist/data/designs/ios-google-maps-swiftui.md +484 -0
- package/dist/data/designs/ios-google-maps.md +451 -0
- package/dist/data/designs/ios-grammarly-android.md +449 -0
- package/dist/data/designs/ios-grammarly-expo.md +383 -0
- package/dist/data/designs/ios-grammarly-swiftui.md +381 -0
- package/dist/data/designs/ios-grammarly.md +392 -0
- package/dist/data/designs/ios-grindr-android.md +494 -0
- package/dist/data/designs/ios-grindr-expo.md +358 -0
- package/dist/data/designs/ios-grindr-swiftui.md +357 -0
- package/dist/data/designs/ios-grindr.md +356 -0
- package/dist/data/designs/ios-grok-android.md +471 -0
- package/dist/data/designs/ios-grok-expo.md +388 -0
- package/dist/data/designs/ios-grok-swiftui.md +397 -0
- package/dist/data/designs/ios-grok.md +357 -0
- package/dist/data/designs/ios-groupme-android.md +551 -0
- package/dist/data/designs/ios-groupme-expo.md +442 -0
- package/dist/data/designs/ios-groupme-swiftui.md +439 -0
- package/dist/data/designs/ios-groupme.md +424 -0
- package/dist/data/designs/ios-grubhub-android.md +506 -0
- package/dist/data/designs/ios-grubhub-expo.md +428 -0
- package/dist/data/designs/ios-grubhub-swiftui.md +408 -0
- package/dist/data/designs/ios-grubhub.md +427 -0
- package/dist/data/designs/ios-happn-android.md +464 -0
- package/dist/data/designs/ios-happn-expo.md +411 -0
- package/dist/data/designs/ios-happn-swiftui.md +401 -0
- package/dist/data/designs/ios-happn.md +400 -0
- package/dist/data/designs/ios-headspace-android.md +688 -0
- package/dist/data/designs/ios-headspace-expo.md +574 -0
- package/dist/data/designs/ios-headspace-swiftui.md +498 -0
- package/dist/data/designs/ios-headspace.md +430 -0
- package/dist/data/designs/ios-hevy-android.md +473 -0
- package/dist/data/designs/ios-hevy-expo.md +405 -0
- package/dist/data/designs/ios-hevy-swiftui.md +367 -0
- package/dist/data/designs/ios-hevy.md +410 -0
- package/dist/data/designs/ios-hinge-android.md +743 -0
- package/dist/data/designs/ios-hinge-expo.md +580 -0
- package/dist/data/designs/ios-hinge-swiftui.md +512 -0
- package/dist/data/designs/ios-hinge.md +405 -0
- package/dist/data/designs/ios-hopper-android.md +440 -0
- package/dist/data/designs/ios-hopper-expo.md +353 -0
- package/dist/data/designs/ios-hopper-swiftui.md +340 -0
- package/dist/data/designs/ios-hopper.md +354 -0
- package/dist/data/designs/ios-hulu-android.md +435 -0
- package/dist/data/designs/ios-hulu-expo.md +331 -0
- package/dist/data/designs/ios-hulu-swiftui.md +374 -0
- package/dist/data/designs/ios-hulu.md +353 -0
- package/dist/data/designs/ios-iheartradio-android.md +481 -0
- package/dist/data/designs/ios-iheartradio-expo.md +417 -0
- package/dist/data/designs/ios-iheartradio-swiftui.md +423 -0
- package/dist/data/designs/ios-iheartradio.md +381 -0
- package/dist/data/designs/ios-imessage-android.md +426 -0
- package/dist/data/designs/ios-imessage-expo.md +384 -0
- package/dist/data/designs/ios-imessage-swiftui.md +378 -0
- package/dist/data/designs/ios-imessage.md +394 -0
- package/dist/data/designs/ios-instacart-android.md +471 -0
- package/dist/data/designs/ios-instacart-expo.md +349 -0
- package/dist/data/designs/ios-instacart-swiftui.md +378 -0
- package/dist/data/designs/ios-instacart.md +360 -0
- package/dist/data/designs/ios-instagram-android.md +646 -0
- package/dist/data/designs/ios-instagram-expo.md +310 -0
- package/dist/data/designs/ios-instagram-swiftui.md +333 -0
- package/dist/data/designs/ios-instagram.md +376 -0
- package/dist/data/designs/ios-jira-android.md +432 -0
- package/dist/data/designs/ios-jira-expo.md +375 -0
- package/dist/data/designs/ios-jira-swiftui.md +433 -0
- package/dist/data/designs/ios-jira.md +424 -0
- package/dist/data/designs/ios-kakaotalk-android.md +491 -0
- package/dist/data/designs/ios-kakaotalk-expo.md +418 -0
- package/dist/data/designs/ios-kakaotalk-swiftui.md +406 -0
- package/dist/data/designs/ios-kakaotalk.md +414 -0
- package/dist/data/designs/ios-kayak-android.md +466 -0
- package/dist/data/designs/ios-kayak-expo.md +407 -0
- package/dist/data/designs/ios-kayak-swiftui.md +426 -0
- package/dist/data/designs/ios-kayak.md +392 -0
- package/dist/data/designs/ios-kick-android.md +487 -0
- package/dist/data/designs/ios-kick-expo.md +434 -0
- package/dist/data/designs/ios-kick-swiftui.md +429 -0
- package/dist/data/designs/ios-kick.md +389 -0
- package/dist/data/designs/ios-kik-android.md +433 -0
- package/dist/data/designs/ios-kik-expo.md +363 -0
- package/dist/data/designs/ios-kik-swiftui.md +363 -0
- package/dist/data/designs/ios-kik.md +392 -0
- package/dist/data/designs/ios-kindle-android.md +439 -0
- package/dist/data/designs/ios-kindle-expo.md +356 -0
- package/dist/data/designs/ios-kindle-swiftui.md +372 -0
- package/dist/data/designs/ios-kindle.md +369 -0
- package/dist/data/designs/ios-klarna-android.md +420 -0
- package/dist/data/designs/ios-klarna-expo.md +349 -0
- package/dist/data/designs/ios-klarna-swiftui.md +360 -0
- package/dist/data/designs/ios-klarna.md +356 -0
- package/dist/data/designs/ios-lemon8-android.md +492 -0
- package/dist/data/designs/ios-lemon8-expo.md +413 -0
- package/dist/data/designs/ios-lemon8-swiftui.md +428 -0
- package/dist/data/designs/ios-lemon8.md +378 -0
- package/dist/data/designs/ios-life360-android.md +474 -0
- package/dist/data/designs/ios-life360-expo.md +375 -0
- package/dist/data/designs/ios-life360-swiftui.md +390 -0
- package/dist/data/designs/ios-life360.md +402 -0
- package/dist/data/designs/ios-line-android.md +517 -0
- package/dist/data/designs/ios-line-expo.md +402 -0
- package/dist/data/designs/ios-line-swiftui.md +361 -0
- package/dist/data/designs/ios-line.md +362 -0
- package/dist/data/designs/ios-linear-android.md +457 -0
- package/dist/data/designs/ios-linear-expo.md +367 -0
- package/dist/data/designs/ios-linear-swiftui.md +401 -0
- package/dist/data/designs/ios-linear.md +368 -0
- package/dist/data/designs/ios-linkedin-android.md +620 -0
- package/dist/data/designs/ios-linkedin-expo.md +469 -0
- package/dist/data/designs/ios-linkedin-swiftui.md +472 -0
- package/dist/data/designs/ios-linkedin.md +444 -0
- package/dist/data/designs/ios-locket-android.md +500 -0
- package/dist/data/designs/ios-locket-expo.md +375 -0
- package/dist/data/designs/ios-locket-swiftui.md +375 -0
- package/dist/data/designs/ios-locket.md +381 -0
- package/dist/data/designs/ios-lyft-android.md +406 -0
- package/dist/data/designs/ios-lyft-expo.md +333 -0
- package/dist/data/designs/ios-lyft-swiftui.md +315 -0
- package/dist/data/designs/ios-lyft.md +337 -0
- package/dist/data/designs/ios-marriott-bonvoy-android.md +488 -0
- package/dist/data/designs/ios-marriott-bonvoy-expo.md +412 -0
- package/dist/data/designs/ios-marriott-bonvoy-swiftui.md +461 -0
- package/dist/data/designs/ios-marriott-bonvoy.md +379 -0
- package/dist/data/designs/ios-mastodon-android.md +476 -0
- package/dist/data/designs/ios-mastodon-expo.md +379 -0
- package/dist/data/designs/ios-mastodon-swiftui.md +349 -0
- package/dist/data/designs/ios-mastodon.md +337 -0
- package/dist/data/designs/ios-match-android.md +496 -0
- package/dist/data/designs/ios-match-expo.md +393 -0
- package/dist/data/designs/ios-match-swiftui.md +450 -0
- package/dist/data/designs/ios-match.md +382 -0
- package/dist/data/designs/ios-max-android.md +454 -0
- package/dist/data/designs/ios-max-expo.md +342 -0
- package/dist/data/designs/ios-max-swiftui.md +332 -0
- package/dist/data/designs/ios-max.md +352 -0
- package/dist/data/designs/ios-mcdonalds-android.md +497 -0
- package/dist/data/designs/ios-mcdonalds-expo.md +382 -0
- package/dist/data/designs/ios-mcdonalds-swiftui.md +394 -0
- package/dist/data/designs/ios-mcdonalds.md +410 -0
- package/dist/data/designs/ios-messenger-android.md +523 -0
- package/dist/data/designs/ios-messenger-expo.md +379 -0
- package/dist/data/designs/ios-messenger-swiftui.md +369 -0
- package/dist/data/designs/ios-messenger.md +361 -0
- package/dist/data/designs/ios-microsoft-teams-android.md +446 -0
- package/dist/data/designs/ios-microsoft-teams-expo.md +362 -0
- package/dist/data/designs/ios-microsoft-teams-swiftui.md +384 -0
- package/dist/data/designs/ios-microsoft-teams.md +359 -0
- package/dist/data/designs/ios-midjourney-android.md +503 -0
- package/dist/data/designs/ios-midjourney-expo.md +406 -0
- package/dist/data/designs/ios-midjourney-swiftui.md +376 -0
- package/dist/data/designs/ios-midjourney.md +407 -0
- package/dist/data/designs/ios-miro-android.md +475 -0
- package/dist/data/designs/ios-miro-expo.md +421 -0
- package/dist/data/designs/ios-miro-swiftui.md +407 -0
- package/dist/data/designs/ios-miro.md +424 -0
- package/dist/data/designs/ios-monday-android.md +426 -0
- package/dist/data/designs/ios-monday-expo.md +382 -0
- package/dist/data/designs/ios-monday-swiftui.md +386 -0
- package/dist/data/designs/ios-monday.md +415 -0
- package/dist/data/designs/ios-monzo-android.md +423 -0
- package/dist/data/designs/ios-monzo-expo.md +391 -0
- package/dist/data/designs/ios-monzo-swiftui.md +371 -0
- package/dist/data/designs/ios-monzo.md +382 -0
- package/dist/data/designs/ios-myfitnesspal-android.md +631 -0
- package/dist/data/designs/ios-myfitnesspal-expo.md +489 -0
- package/dist/data/designs/ios-myfitnesspal-swiftui.md +500 -0
- package/dist/data/designs/ios-myfitnesspal.md +408 -0
- package/dist/data/designs/ios-netflix-android.md +570 -0
- package/dist/data/designs/ios-netflix-expo.md +528 -0
- package/dist/data/designs/ios-netflix-swiftui.md +543 -0
- package/dist/data/designs/ios-netflix.md +476 -0
- package/dist/data/designs/ios-nextdoor-android.md +399 -0
- package/dist/data/designs/ios-nextdoor-expo.md +317 -0
- package/dist/data/designs/ios-nextdoor-swiftui.md +343 -0
- package/dist/data/designs/ios-nextdoor.md +332 -0
- package/dist/data/designs/ios-nike-run-club-android.md +598 -0
- package/dist/data/designs/ios-nike-run-club-expo.md +459 -0
- package/dist/data/designs/ios-nike-run-club-swiftui.md +474 -0
- package/dist/data/designs/ios-nike-run-club.md +400 -0
- package/dist/data/designs/ios-noom-android.md +455 -0
- package/dist/data/designs/ios-noom-expo.md +362 -0
- package/dist/data/designs/ios-noom-swiftui.md +369 -0
- package/dist/data/designs/ios-noom.md +404 -0
- package/dist/data/designs/ios-notion-android.md +575 -0
- package/dist/data/designs/ios-notion-expo.md +543 -0
- package/dist/data/designs/ios-notion-swiftui.md +562 -0
- package/dist/data/designs/ios-notion.md +506 -0
- package/dist/data/designs/ios-nubank-android.md +447 -0
- package/dist/data/designs/ios-nubank-expo.md +391 -0
- package/dist/data/designs/ios-nubank-swiftui.md +391 -0
- package/dist/data/designs/ios-nubank.md +388 -0
- package/dist/data/designs/ios-obsidian-android.md +456 -0
- package/dist/data/designs/ios-obsidian-expo.md +401 -0
- package/dist/data/designs/ios-obsidian-swiftui.md +412 -0
- package/dist/data/designs/ios-obsidian.md +363 -0
- package/dist/data/designs/ios-okcupid-android.md +496 -0
- package/dist/data/designs/ios-okcupid-expo.md +378 -0
- package/dist/data/designs/ios-okcupid-swiftui.md +343 -0
- package/dist/data/designs/ios-okcupid.md +364 -0
- package/dist/data/designs/ios-opentable-android.md +426 -0
- package/dist/data/designs/ios-opentable-expo.md +350 -0
- package/dist/data/designs/ios-opentable-swiftui.md +328 -0
- package/dist/data/designs/ios-opentable.md +411 -0
- package/dist/data/designs/ios-oura-android.md +426 -0
- package/dist/data/designs/ios-oura-expo.md +353 -0
- package/dist/data/designs/ios-oura-swiftui.md +327 -0
- package/dist/data/designs/ios-oura.md +356 -0
- package/dist/data/designs/ios-overcast-android.md +508 -0
- package/dist/data/designs/ios-overcast-expo.md +420 -0
- package/dist/data/designs/ios-overcast-swiftui.md +427 -0
- package/dist/data/designs/ios-overcast.md +414 -0
- package/dist/data/designs/ios-pandora-android.md +477 -0
- package/dist/data/designs/ios-pandora-expo.md +406 -0
- package/dist/data/designs/ios-pandora-swiftui.md +413 -0
- package/dist/data/designs/ios-pandora.md +366 -0
- package/dist/data/designs/ios-panera-android.md +414 -0
- package/dist/data/designs/ios-panera-expo.md +352 -0
- package/dist/data/designs/ios-panera-swiftui.md +369 -0
- package/dist/data/designs/ios-panera.md +412 -0
- package/dist/data/designs/ios-paramount-plus-android.md +415 -0
- package/dist/data/designs/ios-paramount-plus-expo.md +363 -0
- package/dist/data/designs/ios-paramount-plus-swiftui.md +372 -0
- package/dist/data/designs/ios-paramount-plus.md +400 -0
- package/dist/data/designs/ios-paypal-android.md +535 -0
- package/dist/data/designs/ios-paypal-expo.md +429 -0
- package/dist/data/designs/ios-paypal-swiftui.md +466 -0
- package/dist/data/designs/ios-paypal.md +393 -0
- package/dist/data/designs/ios-peacock-android.md +453 -0
- package/dist/data/designs/ios-peacock-expo.md +393 -0
- package/dist/data/designs/ios-peacock-swiftui.md +420 -0
- package/dist/data/designs/ios-peacock.md +411 -0
- package/dist/data/designs/ios-peloton-android.md +470 -0
- package/dist/data/designs/ios-peloton-expo.md +401 -0
- package/dist/data/designs/ios-peloton-swiftui.md +409 -0
- package/dist/data/designs/ios-peloton.md +415 -0
- package/dist/data/designs/ios-perplexity-android.md +553 -0
- package/dist/data/designs/ios-perplexity-expo.md +703 -0
- package/dist/data/designs/ios-perplexity-swiftui.md +602 -0
- package/dist/data/designs/ios-perplexity.md +453 -0
- package/dist/data/designs/ios-pinterest-android.md +551 -0
- package/dist/data/designs/ios-pinterest-expo.md +466 -0
- package/dist/data/designs/ios-pinterest-swiftui.md +464 -0
- package/dist/data/designs/ios-pinterest.md +427 -0
- package/dist/data/designs/ios-plenty-of-fish-android.md +462 -0
- package/dist/data/designs/ios-plenty-of-fish-expo.md +380 -0
- package/dist/data/designs/ios-plenty-of-fish-swiftui.md +429 -0
- package/dist/data/designs/ios-plenty-of-fish.md +387 -0
- package/dist/data/designs/ios-plex-android.md +403 -0
- package/dist/data/designs/ios-plex-expo.md +333 -0
- package/dist/data/designs/ios-plex-swiftui.md +349 -0
- package/dist/data/designs/ios-plex.md +379 -0
- package/dist/data/designs/ios-pluto-tv-android.md +413 -0
- package/dist/data/designs/ios-pluto-tv-expo.md +379 -0
- package/dist/data/designs/ios-pluto-tv-swiftui.md +411 -0
- package/dist/data/designs/ios-pluto-tv.md +362 -0
- package/dist/data/designs/ios-pocket-casts-android.md +492 -0
- package/dist/data/designs/ios-pocket-casts-expo.md +430 -0
- package/dist/data/designs/ios-pocket-casts-swiftui.md +479 -0
- package/dist/data/designs/ios-pocket-casts.md +427 -0
- package/dist/data/designs/ios-postman-android.md +472 -0
- package/dist/data/designs/ios-postman-expo.md +372 -0
- package/dist/data/designs/ios-postman-swiftui.md +377 -0
- package/dist/data/designs/ios-postman.md +405 -0
- package/dist/data/designs/ios-prime-video-android.md +447 -0
- package/dist/data/designs/ios-prime-video-expo.md +365 -0
- package/dist/data/designs/ios-prime-video-swiftui.md +333 -0
- package/dist/data/designs/ios-prime-video.md +351 -0
- package/dist/data/designs/ios-quora-android.md +513 -0
- package/dist/data/designs/ios-quora-expo.md +362 -0
- package/dist/data/designs/ios-quora-swiftui.md +375 -0
- package/dist/data/designs/ios-quora.md +359 -0
- package/dist/data/designs/ios-raya-android.md +401 -0
- package/dist/data/designs/ios-raya-expo.md +366 -0
- package/dist/data/designs/ios-raya-swiftui.md +393 -0
- package/dist/data/designs/ios-raya.md +366 -0
- package/dist/data/designs/ios-reddit-android.md +605 -0
- package/dist/data/designs/ios-reddit-expo.md +553 -0
- package/dist/data/designs/ios-reddit-swiftui.md +514 -0
- package/dist/data/designs/ios-reddit.md +460 -0
- package/dist/data/designs/ios-resy-android.md +430 -0
- package/dist/data/designs/ios-resy-expo.md +344 -0
- package/dist/data/designs/ios-resy-swiftui.md +330 -0
- package/dist/data/designs/ios-resy.md +412 -0
- package/dist/data/designs/ios-revolut-android.md +433 -0
- package/dist/data/designs/ios-revolut-expo.md +379 -0
- package/dist/data/designs/ios-revolut-swiftui.md +372 -0
- package/dist/data/designs/ios-revolut.md +353 -0
- package/dist/data/designs/ios-robinhood-android.md +587 -0
- package/dist/data/designs/ios-robinhood-expo.md +416 -0
- package/dist/data/designs/ios-robinhood-swiftui.md +431 -0
- package/dist/data/designs/ios-robinhood.md +380 -0
- package/dist/data/designs/ios-runna-android.md +485 -0
- package/dist/data/designs/ios-runna-expo.md +388 -0
- package/dist/data/designs/ios-runna-swiftui.md +413 -0
- package/dist/data/designs/ios-runna.md +390 -0
- package/dist/data/designs/ios-shazam-android.md +436 -0
- package/dist/data/designs/ios-shazam-expo.md +348 -0
- package/dist/data/designs/ios-shazam-swiftui.md +394 -0
- package/dist/data/designs/ios-shazam.md +338 -0
- package/dist/data/designs/ios-signal-android.md +462 -0
- package/dist/data/designs/ios-signal-expo.md +361 -0
- package/dist/data/designs/ios-signal-swiftui.md +350 -0
- package/dist/data/designs/ios-signal.md +357 -0
- package/dist/data/designs/ios-skype-android.md +398 -0
- package/dist/data/designs/ios-skype-expo.md +368 -0
- package/dist/data/designs/ios-skype-swiftui.md +369 -0
- package/dist/data/designs/ios-skype.md +406 -0
- package/dist/data/designs/ios-skyscanner-android.md +451 -0
- package/dist/data/designs/ios-skyscanner-expo.md +423 -0
- package/dist/data/designs/ios-skyscanner-swiftui.md +419 -0
- package/dist/data/designs/ios-skyscanner.md +421 -0
- package/dist/data/designs/ios-slack-android.md +695 -0
- package/dist/data/designs/ios-slack-expo.md +601 -0
- package/dist/data/designs/ios-slack-swiftui.md +594 -0
- package/dist/data/designs/ios-slack.md +474 -0
- package/dist/data/designs/ios-sleep-cycle-android.md +436 -0
- package/dist/data/designs/ios-sleep-cycle-expo.md +380 -0
- package/dist/data/designs/ios-sleep-cycle-swiftui.md +409 -0
- package/dist/data/designs/ios-sleep-cycle.md +386 -0
- package/dist/data/designs/ios-snapchat-android.md +671 -0
- package/dist/data/designs/ios-snapchat-expo.md +528 -0
- package/dist/data/designs/ios-snapchat-swiftui.md +557 -0
- package/dist/data/designs/ios-snapchat.md +478 -0
- package/dist/data/designs/ios-sofi-android.md +446 -0
- package/dist/data/designs/ios-sofi-expo.md +387 -0
- package/dist/data/designs/ios-sofi-swiftui.md +379 -0
- package/dist/data/designs/ios-sofi.md +385 -0
- package/dist/data/designs/ios-soundcloud-android.md +488 -0
- package/dist/data/designs/ios-soundcloud-expo.md +364 -0
- package/dist/data/designs/ios-soundcloud-swiftui.md +399 -0
- package/dist/data/designs/ios-soundcloud.md +359 -0
- package/dist/data/designs/ios-southwest-android.md +446 -0
- package/dist/data/designs/ios-southwest-expo.md +389 -0
- package/dist/data/designs/ios-southwest-swiftui.md +405 -0
- package/dist/data/designs/ios-southwest.md +405 -0
- package/dist/data/designs/ios-spotify-android.md +427 -0
- package/dist/data/designs/ios-spotify-expo.md +321 -0
- package/dist/data/designs/ios-spotify-swiftui.md +312 -0
- package/dist/data/designs/ios-spotify.md +366 -0
- package/dist/data/designs/ios-starbucks-android.md +622 -0
- package/dist/data/designs/ios-starbucks-expo.md +497 -0
- package/dist/data/designs/ios-starbucks-swiftui.md +500 -0
- package/dist/data/designs/ios-starbucks.md +440 -0
- package/dist/data/designs/ios-strava-android.md +656 -0
- package/dist/data/designs/ios-strava-expo.md +501 -0
- package/dist/data/designs/ios-strava-swiftui.md +465 -0
- package/dist/data/designs/ios-strava.md +433 -0
- package/dist/data/designs/ios-strong-android.md +479 -0
- package/dist/data/designs/ios-strong-expo.md +396 -0
- package/dist/data/designs/ios-strong-swiftui.md +363 -0
- package/dist/data/designs/ios-strong.md +415 -0
- package/dist/data/designs/ios-substack-android.md +440 -0
- package/dist/data/designs/ios-substack-expo.md +395 -0
- package/dist/data/designs/ios-substack-swiftui.md +371 -0
- package/dist/data/designs/ios-substack.md +372 -0
- package/dist/data/designs/ios-swiggy-android.md +549 -0
- package/dist/data/designs/ios-swiggy-expo.md +443 -0
- package/dist/data/designs/ios-swiggy-swiftui.md +471 -0
- package/dist/data/designs/ios-swiggy.md +432 -0
- package/dist/data/designs/ios-taco-bell-android.md +529 -0
- package/dist/data/designs/ios-taco-bell-expo.md +407 -0
- package/dist/data/designs/ios-taco-bell-swiftui.md +419 -0
- package/dist/data/designs/ios-taco-bell.md +430 -0
- package/dist/data/designs/ios-telegram-android.md +676 -0
- package/dist/data/designs/ios-telegram-expo.md +594 -0
- package/dist/data/designs/ios-telegram-swiftui.md +518 -0
- package/dist/data/designs/ios-telegram.md +458 -0
- package/dist/data/designs/ios-the-league-android.md +454 -0
- package/dist/data/designs/ios-the-league-expo.md +391 -0
- package/dist/data/designs/ios-the-league-swiftui.md +409 -0
- package/dist/data/designs/ios-the-league.md +368 -0
- package/dist/data/designs/ios-things-3-android.md +421 -0
- package/dist/data/designs/ios-things-3-expo.md +351 -0
- package/dist/data/designs/ios-things-3-swiftui.md +359 -0
- package/dist/data/designs/ios-things-3.md +339 -0
- package/dist/data/designs/ios-threads-android.md +588 -0
- package/dist/data/designs/ios-threads-expo.md +468 -0
- package/dist/data/designs/ios-threads-swiftui.md +452 -0
- package/dist/data/designs/ios-threads.md +424 -0
- package/dist/data/designs/ios-threema-android.md +496 -0
- package/dist/data/designs/ios-threema-expo.md +415 -0
- package/dist/data/designs/ios-threema-swiftui.md +446 -0
- package/dist/data/designs/ios-threema.md +397 -0
- package/dist/data/designs/ios-tidal-android.md +398 -0
- package/dist/data/designs/ios-tidal-expo.md +323 -0
- package/dist/data/designs/ios-tidal-swiftui.md +340 -0
- package/dist/data/designs/ios-tidal.md +351 -0
- package/dist/data/designs/ios-tiktok-android.md +654 -0
- package/dist/data/designs/ios-tiktok-expo.md +578 -0
- package/dist/data/designs/ios-tiktok-swiftui.md +595 -0
- package/dist/data/designs/ios-tiktok.md +387 -0
- package/dist/data/designs/ios-tinder-android.md +681 -0
- package/dist/data/designs/ios-tinder-expo.md +512 -0
- package/dist/data/designs/ios-tinder-swiftui.md +497 -0
- package/dist/data/designs/ios-tinder.md +415 -0
- package/dist/data/designs/ios-todoist-android.md +711 -0
- package/dist/data/designs/ios-todoist-expo.md +607 -0
- package/dist/data/designs/ios-todoist-swiftui.md +573 -0
- package/dist/data/designs/ios-todoist.md +418 -0
- package/dist/data/designs/ios-trello-android.md +412 -0
- package/dist/data/designs/ios-trello-expo.md +332 -0
- package/dist/data/designs/ios-trello-swiftui.md +359 -0
- package/dist/data/designs/ios-trello.md +344 -0
- package/dist/data/designs/ios-tripadvisor-android.md +450 -0
- package/dist/data/designs/ios-tripadvisor-expo.md +322 -0
- package/dist/data/designs/ios-tripadvisor-swiftui.md +326 -0
- package/dist/data/designs/ios-tripadvisor.md +356 -0
- package/dist/data/designs/ios-tubi-android.md +453 -0
- package/dist/data/designs/ios-tubi-expo.md +419 -0
- package/dist/data/designs/ios-tubi-swiftui.md +463 -0
- package/dist/data/designs/ios-tubi.md +383 -0
- package/dist/data/designs/ios-tumblr-android.md +462 -0
- package/dist/data/designs/ios-tumblr-expo.md +374 -0
- package/dist/data/designs/ios-tumblr-swiftui.md +380 -0
- package/dist/data/designs/ios-tumblr.md +337 -0
- package/dist/data/designs/ios-turo-android.md +500 -0
- package/dist/data/designs/ios-turo-expo.md +454 -0
- package/dist/data/designs/ios-turo-swiftui.md +447 -0
- package/dist/data/designs/ios-turo.md +392 -0
- package/dist/data/designs/ios-twitch-android.md +412 -0
- package/dist/data/designs/ios-twitch-expo.md +342 -0
- package/dist/data/designs/ios-twitch-swiftui.md +328 -0
- package/dist/data/designs/ios-twitch.md +354 -0
- package/dist/data/designs/ios-uber-android.md +637 -0
- package/dist/data/designs/ios-uber-eats-android.md +502 -0
- package/dist/data/designs/ios-uber-eats-expo.md +407 -0
- package/dist/data/designs/ios-uber-eats-swiftui.md +393 -0
- package/dist/data/designs/ios-uber-eats.md +353 -0
- package/dist/data/designs/ios-uber-expo.md +512 -0
- package/dist/data/designs/ios-uber-swiftui.md +483 -0
- package/dist/data/designs/ios-uber.md +395 -0
- package/dist/data/designs/ios-united-airlines-android.md +494 -0
- package/dist/data/designs/ios-united-airlines-expo.md +408 -0
- package/dist/data/designs/ios-united-airlines-swiftui.md +453 -0
- package/dist/data/designs/ios-united-airlines.md +388 -0
- package/dist/data/designs/ios-venmo-android.md +654 -0
- package/dist/data/designs/ios-venmo-expo.md +458 -0
- package/dist/data/designs/ios-venmo-swiftui.md +469 -0
- package/dist/data/designs/ios-venmo.md +436 -0
- package/dist/data/designs/ios-vercel-android.md +454 -0
- package/dist/data/designs/ios-vercel-expo.md +387 -0
- package/dist/data/designs/ios-vercel-swiftui.md +374 -0
- package/dist/data/designs/ios-vercel.md +399 -0
- package/dist/data/designs/ios-vero-android.md +498 -0
- package/dist/data/designs/ios-vero-expo.md +376 -0
- package/dist/data/designs/ios-vero-swiftui.md +374 -0
- package/dist/data/designs/ios-vero.md +394 -0
- package/dist/data/designs/ios-viber-android.md +377 -0
- package/dist/data/designs/ios-viber-expo.md +375 -0
- package/dist/data/designs/ios-viber-swiftui.md +378 -0
- package/dist/data/designs/ios-viber.md +403 -0
- package/dist/data/designs/ios-vimeo-android.md +465 -0
- package/dist/data/designs/ios-vimeo-expo.md +347 -0
- package/dist/data/designs/ios-vimeo-swiftui.md +383 -0
- package/dist/data/designs/ios-vimeo.md +403 -0
- package/dist/data/designs/ios-vrbo-android.md +488 -0
- package/dist/data/designs/ios-vrbo-expo.md +395 -0
- package/dist/data/designs/ios-vrbo-swiftui.md +421 -0
- package/dist/data/designs/ios-vrbo.md +383 -0
- package/dist/data/designs/ios-vsco-android.md +490 -0
- package/dist/data/designs/ios-vsco-expo.md +413 -0
- package/dist/data/designs/ios-vsco-swiftui.md +449 -0
- package/dist/data/designs/ios-vsco.md +412 -0
- package/dist/data/designs/ios-walmart-android.md +479 -0
- package/dist/data/designs/ios-walmart-expo.md +370 -0
- package/dist/data/designs/ios-walmart-swiftui.md +387 -0
- package/dist/data/designs/ios-walmart.md +366 -0
- package/dist/data/designs/ios-waze-android.md +695 -0
- package/dist/data/designs/ios-waze-expo.md +587 -0
- package/dist/data/designs/ios-waze-swiftui.md +601 -0
- package/dist/data/designs/ios-waze.md +453 -0
- package/dist/data/designs/ios-webull-android.md +429 -0
- package/dist/data/designs/ios-webull-expo.md +384 -0
- package/dist/data/designs/ios-webull-swiftui.md +406 -0
- package/dist/data/designs/ios-webull.md +375 -0
- package/dist/data/designs/ios-wechat-android.md +545 -0
- package/dist/data/designs/ios-wechat-expo.md +398 -0
- package/dist/data/designs/ios-wechat-swiftui.md +394 -0
- package/dist/data/designs/ios-wechat.md +363 -0
- package/dist/data/designs/ios-whatsapp-android.md +568 -0
- package/dist/data/designs/ios-whatsapp-expo.md +502 -0
- package/dist/data/designs/ios-whatsapp-swiftui.md +483 -0
- package/dist/data/designs/ios-whatsapp.md +425 -0
- package/dist/data/designs/ios-whoop-android.md +635 -0
- package/dist/data/designs/ios-whoop-expo.md +562 -0
- package/dist/data/designs/ios-whoop-swiftui.md +553 -0
- package/dist/data/designs/ios-whoop.md +419 -0
- package/dist/data/designs/ios-wise-android.md +428 -0
- package/dist/data/designs/ios-wise-expo.md +357 -0
- package/dist/data/designs/ios-wise-swiftui.md +356 -0
- package/dist/data/designs/ios-wise.md +361 -0
- package/dist/data/designs/ios-x-twitter-android.md +618 -0
- package/dist/data/designs/ios-x-twitter-expo.md +442 -0
- package/dist/data/designs/ios-x-twitter-swiftui.md +429 -0
- package/dist/data/designs/ios-x-twitter.md +432 -0
- package/dist/data/designs/ios-yelp-android.md +455 -0
- package/dist/data/designs/ios-yelp-expo.md +397 -0
- package/dist/data/designs/ios-yelp-swiftui.md +369 -0
- package/dist/data/designs/ios-yelp.md +398 -0
- package/dist/data/designs/ios-youtube-android.md +668 -0
- package/dist/data/designs/ios-youtube-expo.md +435 -0
- package/dist/data/designs/ios-youtube-music-android.md +450 -0
- package/dist/data/designs/ios-youtube-music-expo.md +404 -0
- package/dist/data/designs/ios-youtube-music-swiftui.md +434 -0
- package/dist/data/designs/ios-youtube-music.md +381 -0
- package/dist/data/designs/ios-youtube-swiftui.md +496 -0
- package/dist/data/designs/ios-youtube.md +466 -0
- package/dist/data/designs/ios-zalo-android.md +510 -0
- package/dist/data/designs/ios-zalo-expo.md +389 -0
- package/dist/data/designs/ios-zalo-swiftui.md +431 -0
- package/dist/data/designs/ios-zalo.md +388 -0
- package/dist/data/designs/ios-zomato-android.md +527 -0
- package/dist/data/designs/ios-zomato-expo.md +437 -0
- package/dist/data/designs/ios-zomato-swiftui.md +433 -0
- package/dist/data/designs/ios-zomato.md +439 -0
- package/dist/data/designs/ios-zoom-android.md +423 -0
- package/dist/data/designs/ios-zoom-expo.md +355 -0
- package/dist/data/designs/ios-zoom-swiftui.md +379 -0
- package/dist/data/designs/ios-zoom.md +357 -0
- package/dist/data/designs/ios-zwift-android.md +402 -0
- package/dist/data/designs/ios-zwift-expo.md +402 -0
- package/dist/data/designs/ios-zwift-swiftui.md +378 -0
- package/dist/data/designs/ios-zwift.md +411 -0
- package/dist/data/designs/kraken.md +125 -0
- package/dist/data/designs/lamborghini.md +288 -0
- package/dist/data/designs/linear.app.md +548 -0
- package/dist/data/designs/lovable.md +298 -0
- package/dist/data/designs/mastercard.md +365 -0
- package/dist/data/designs/material-clean.md +289 -0
- package/dist/data/designs/meta.md +683 -0
- package/dist/data/designs/midnight-pro.md +256 -0
- package/dist/data/designs/minimal-brutalist.md +245 -0
- package/dist/data/designs/minimax.md +746 -0
- package/dist/data/designs/mintlify.md +852 -0
- package/dist/data/designs/miro.md +825 -0
- package/dist/data/designs/mistral.ai.md +773 -0
- package/dist/data/designs/mongodb.md +767 -0
- package/dist/data/designs/nike.md +575 -0
- package/dist/data/designs/nintendo-2001.md +649 -0
- package/dist/data/designs/notion.md +821 -0
- package/dist/data/designs/nova-mobile-design-md.md +235 -0
- package/dist/data/designs/nvidia.md +640 -0
- package/dist/data/designs/ollama.md +539 -0
- package/dist/data/designs/opencode.ai.md +521 -0
- package/dist/data/designs/pinterest.md +597 -0
- package/dist/data/designs/playful-color.md +253 -0
- package/dist/data/designs/playstation.md +661 -0
- package/dist/data/designs/posthog.md +690 -0
- package/dist/data/designs/raycast.md +669 -0
- package/dist/data/designs/renault.md +589 -0
- package/dist/data/designs/replicate.md +616 -0
- package/dist/data/designs/resend.md +585 -0
- package/dist/data/designs/revolut.md +636 -0
- package/dist/data/designs/runwayml.md +244 -0
- package/dist/data/designs/sanity.md +357 -0
- package/dist/data/designs/sentry.md +551 -0
- package/dist/data/designs/shopify.md +516 -0
- package/dist/data/designs/slack.md +482 -0
- package/dist/data/designs/soft-glass.md +258 -0
- package/dist/data/designs/spacex.md +363 -0
- package/dist/data/designs/spotify.md +246 -0
- package/dist/data/designs/starbucks.md +580 -0
- package/dist/data/designs/stripe.md +487 -0
- package/dist/data/designs/supabase.md +462 -0
- package/dist/data/designs/superhuman.md +448 -0
- package/dist/data/designs/tesla.md +286 -0
- package/dist/data/designs/theverge.md +339 -0
- package/dist/data/designs/together.ai.md +633 -0
- package/dist/data/designs/uber.md +636 -0
- package/dist/data/designs/vercel.md +736 -0
- package/dist/data/designs/vodafone.md +538 -0
- package/dist/data/designs/voltagent.md +521 -0
- package/dist/data/designs/warp.md +526 -0
- package/dist/data/designs/webflow.md +588 -0
- package/dist/data/designs/wired.md +497 -0
- package/dist/data/designs/wise.md +544 -0
- package/dist/data/designs/x.ai.md +465 -0
- package/dist/data/designs/zapier.md +537 -0
- package/dist/data/index.json +2600 -0
- package/dist/httpServer.js +83 -0
- package/dist/index.js +13 -0
- package/dist/server.js +32 -0
- package/dist/services/designStore.js +59 -0
- package/dist/services/linter.js +87 -0
- package/dist/services/sectionParser.js +154 -0
- package/dist/tools/authoring.js +131 -0
- package/dist/tools/browse.js +232 -0
- package/dist/tools/diff.js +88 -0
- package/dist/tools/getDesign.js +216 -0
- package/dist/tools/guardrails.js +87 -0
- package/dist/tools/lint.js +104 -0
- package/dist/types.js +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
# ChatGPT (iOS) — Jetpack Compose Implementation Guide
|
|
2
|
+
|
|
3
|
+
Companion to [DESIGN.md](DESIGN.md) — the framework-neutral spec. This file ports ChatGPT's visual language to **Android with Jetpack Compose (Material 3)**: light + dark color token objects, a `Typography` set, paste-ready `@Composable`s, the black/white send button, the markdown atoms, and the full-screen voice-mode sphere.
|
|
4
|
+
|
|
5
|
+
> Why a Compose guide for an iOS-referenced app? The DESIGN.md tokens are platform-neutral. This file keeps the *visual* identity (ChatGPT's monochromatic minimalism, the circular send button, bubble-less assistant text, the pulsing blue voice sphere) while making everything idiomatic Android — a custom asymmetric `Shape` for the user bubble, `ModalNavigationDrawer` for the conversation sidebar, an `Animatable`-driven `Canvas` sphere, `sp`/`dp` instead of `pt`. ChatGPT is radically flat — shadows appear only on ephemeral floating UI.
|
|
6
|
+
|
|
7
|
+
Assumes Compose BOM `2024.09+`, Kotlin `2.0+`, `minSdk 24`, Material 3, [Coil](https://coil-kt.github.io/coil/) `2.6+` for attachment thumbnails, and a markdown renderer such as [compose-markdown](https://github.com/jeziellago/compose-markdown) or `Markwon` for assistant content (headings, code, tables, LaTeX). No color extraction, so no `androidx.palette`.
|
|
8
|
+
|
|
9
|
+
## 1. Color Tokens
|
|
10
|
+
|
|
11
|
+
ChatGPT ships first-class light **and** dark. Define both palettes, then select per system theme.
|
|
12
|
+
|
|
13
|
+
```kotlin
|
|
14
|
+
// ui/theme/GptColors.kt
|
|
15
|
+
import androidx.compose.ui.graphics.Color
|
|
16
|
+
|
|
17
|
+
object GptColors {
|
|
18
|
+
// Canvas — pure white light / charcoal (NOT true black) dark
|
|
19
|
+
val Canvas = Color(0xFFFFFFFF)
|
|
20
|
+
val DarkCanvas = Color(0xFF212121)
|
|
21
|
+
|
|
22
|
+
// Sidebar (darker than canvas — signature treatment)
|
|
23
|
+
val SidebarLight = Color(0xFFF9F9F9)
|
|
24
|
+
val SidebarDark = Color(0xFF181818)
|
|
25
|
+
val SidebarActive = Color(0xFFECECEC)
|
|
26
|
+
val SidebarActiveDk = Color(0xFF2F2F2F)
|
|
27
|
+
val SidebarHover = Color(0xFFF0F0F0)
|
|
28
|
+
val SidebarHoverDk = Color(0xFF252525)
|
|
29
|
+
val Divider = Color(0xFFE5E5E5)
|
|
30
|
+
val DividerDark = Color(0xFF424242)
|
|
31
|
+
|
|
32
|
+
// Text
|
|
33
|
+
val TextPrimary = Color(0xFF0D0D0D) // softer than pure #000
|
|
34
|
+
val TextSecondary = Color(0xFF676767) // placeholder, timestamps
|
|
35
|
+
val TextTertiary = Color(0xFF8E8E8E) // disabled
|
|
36
|
+
val DarkTextPrimary = Color(0xFFECECEC)
|
|
37
|
+
val DarkTextSecondary = Color(0xFFB4B4B4)
|
|
38
|
+
val DarkTextTertiary = Color(0xFF707070)
|
|
39
|
+
|
|
40
|
+
// User message bubble (assistant text has NO bubble)
|
|
41
|
+
val UserBubbleLight = Color(0xFFF7F7F8)
|
|
42
|
+
val UserBubbleDark = Color(0xFF2F2F2F)
|
|
43
|
+
|
|
44
|
+
// Code
|
|
45
|
+
val CodeBlockLight = Color(0xFFF7F7F8)
|
|
46
|
+
val CodeBlockDark = Color(0xFF1E1E1E)
|
|
47
|
+
val CodeInlineLight = Color(0xFFF0F0F0)
|
|
48
|
+
val CodeInlineDark = Color(0xFF424242)
|
|
49
|
+
val CodeBorderLight = Color(0xFFE5E5E5)
|
|
50
|
+
val CodeBorderDark = Color(0xFF363636)
|
|
51
|
+
|
|
52
|
+
// Send button (inverse of canvas)
|
|
53
|
+
val SendLight = Color(0xFF0D0D0D)
|
|
54
|
+
val SendDark = Color(0xFFFFFFFF)
|
|
55
|
+
val SendDisabled = Color(0xFFCCCCCC)
|
|
56
|
+
val SendDisabledDk = Color(0xFF4D4D4D)
|
|
57
|
+
|
|
58
|
+
// Semantic — monochromatic; blue only on links
|
|
59
|
+
val LinkBlue = Color(0xFF2A7FFF)
|
|
60
|
+
val LegacyGreen = Color(0xFF10A37F) // largely retired — wordmark / legacy UI only
|
|
61
|
+
val ErrorRed = Color(0xFFE53E3E)
|
|
62
|
+
val WarningOrange = Color(0xFFD97706)
|
|
63
|
+
|
|
64
|
+
// Voice-mode sphere gradient
|
|
65
|
+
val VoiceBlue1 = Color(0xFF3B82F6)
|
|
66
|
+
val VoiceBlue2 = Color(0xFF60A5FA)
|
|
67
|
+
val VoiceBlue3 = Color(0xFF93C5FD)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Wire both into Material 3 schemes and switch on the system setting. ChatGPT supports both modes equally, so unlike most strong-brand apps this one *does* provide a real `lightColorScheme` and `darkColorScheme`.
|
|
72
|
+
|
|
73
|
+
```kotlin
|
|
74
|
+
// ui/theme/Theme.kt
|
|
75
|
+
import androidx.compose.foundation.isSystemInDarkTheme
|
|
76
|
+
import androidx.compose.material3.darkColorScheme
|
|
77
|
+
import androidx.compose.material3.lightColorScheme
|
|
78
|
+
import androidx.compose.material3.MaterialTheme
|
|
79
|
+
|
|
80
|
+
private val GptLight = lightColorScheme(
|
|
81
|
+
primary = GptColors.SendLight, // monochrome — the "accent" is ink
|
|
82
|
+
onPrimary = GptColors.Canvas,
|
|
83
|
+
background = GptColors.Canvas,
|
|
84
|
+
onBackground = GptColors.TextPrimary,
|
|
85
|
+
surface = GptColors.Canvas,
|
|
86
|
+
onSurface = GptColors.TextPrimary,
|
|
87
|
+
surfaceVariant = GptColors.UserBubbleLight,
|
|
88
|
+
outline = GptColors.Divider,
|
|
89
|
+
error = GptColors.ErrorRed,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
private val GptDark = darkColorScheme(
|
|
93
|
+
primary = GptColors.SendDark,
|
|
94
|
+
onPrimary = GptColors.TextPrimary,
|
|
95
|
+
background = GptColors.DarkCanvas,
|
|
96
|
+
onBackground = GptColors.DarkTextPrimary,
|
|
97
|
+
surface = GptColors.DarkCanvas,
|
|
98
|
+
onSurface = GptColors.DarkTextPrimary,
|
|
99
|
+
surfaceVariant = GptColors.UserBubbleDark,
|
|
100
|
+
outline = GptColors.DividerDark,
|
|
101
|
+
error = GptColors.ErrorRed,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@Composable
|
|
105
|
+
fun GptTheme(content: @Composable () -> Unit) =
|
|
106
|
+
MaterialTheme(
|
|
107
|
+
colorScheme = if (isSystemInDarkTheme()) GptDark else GptLight,
|
|
108
|
+
typography = GptTypography,
|
|
109
|
+
content = content,
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 2. Typography
|
|
114
|
+
|
|
115
|
+
ChatGPT uses **Söhne** (Klim Type Foundry, commercially licensed), falling back to Inter, then SF Pro. Drop Söhne/Inter TTFs in `res/font/` (lowercase, snake_case); Roboto is the Android system fallback. Weights are concentrated at 400/500/600 — no bold, no thin. Code is always monospace (Menlo / SF Mono on iOS → bundle JetBrains Mono / use the platform monospace family).
|
|
116
|
+
|
|
117
|
+
```kotlin
|
|
118
|
+
// ui/theme/GptType.kt
|
|
119
|
+
import androidx.compose.material3.Typography
|
|
120
|
+
import androidx.compose.ui.text.TextStyle
|
|
121
|
+
import androidx.compose.ui.text.font.Font
|
|
122
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
123
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
124
|
+
import androidx.compose.ui.unit.sp
|
|
125
|
+
|
|
126
|
+
val Sohne = FontFamily(
|
|
127
|
+
Font(R.font.sohne_buch, FontWeight.Normal), // 400 "Buch"
|
|
128
|
+
Font(R.font.sohne_kraftig, FontWeight.Medium), // 500 "Kräftig"
|
|
129
|
+
Font(R.font.sohne_halbfett, FontWeight.SemiBold), // 600 "Halbfett"
|
|
130
|
+
)
|
|
131
|
+
val GptMono = FontFamily(Font(R.font.jetbrains_mono_regular, FontWeight.Normal)) // code only
|
|
132
|
+
|
|
133
|
+
// Named ramp — mirrors DESIGN.md §3 exactly (pt → sp 1:1, same weights/tracking)
|
|
134
|
+
object GptText {
|
|
135
|
+
val Body = TextStyle(Sohne, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp)
|
|
136
|
+
val BodyCompact = TextStyle(Sohne, fontWeight = FontWeight.Normal, fontSize = 15.sp, lineHeight = 22.sp)
|
|
137
|
+
val H1 = TextStyle(Sohne, fontWeight = FontWeight.SemiBold, fontSize = 24.sp, lineHeight = 30.sp, letterSpacing = (-0.2).sp)
|
|
138
|
+
val H2 = TextStyle(Sohne, fontWeight = FontWeight.SemiBold, fontSize = 20.sp, lineHeight = 26.sp, letterSpacing = (-0.15).sp)
|
|
139
|
+
val H3 = TextStyle(Sohne, fontWeight = FontWeight.SemiBold, fontSize = 17.sp, lineHeight = 23.sp, letterSpacing = (-0.1).sp)
|
|
140
|
+
val H4 = TextStyle(Sohne, fontWeight = FontWeight.SemiBold, fontSize = 16.sp, lineHeight = 22.sp)
|
|
141
|
+
val ModelChip = TextStyle(Sohne, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 14.sp)
|
|
142
|
+
val SidebarTitle = TextStyle(Sohne, fontWeight = FontWeight.Medium, fontSize = 15.sp, lineHeight = 20.sp)
|
|
143
|
+
val SidebarSection = TextStyle(Sohne, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 12.sp, letterSpacing = 0.2.sp)
|
|
144
|
+
val Button = TextStyle(Sohne, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 14.sp)
|
|
145
|
+
val Meta = TextStyle(Sohne, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 16.sp)
|
|
146
|
+
val Placeholder = TextStyle(Sohne, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 22.sp)
|
|
147
|
+
val LinkText = TextStyle(Sohne, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp)
|
|
148
|
+
val CodeInline = TextStyle(GptMono, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 18.sp)
|
|
149
|
+
val CodeBlock = TextStyle(GptMono, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 20.sp)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Map onto Material 3 slots so stock components inherit the brand
|
|
153
|
+
val GptTypography = Typography(
|
|
154
|
+
headlineLarge = GptText.H1,
|
|
155
|
+
headlineSmall = GptText.H2,
|
|
156
|
+
titleMedium = GptText.H3,
|
|
157
|
+
bodyMedium = GptText.Body,
|
|
158
|
+
labelSmall = GptText.SidebarSection,
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Typography follows **document rhythm, not UI rhythm** — 1.5 line-height on body (`24.sp / 16.sp`), markdown headings stepping down. Code styles must never be substituted away from monospace.
|
|
163
|
+
|
|
164
|
+
## 3. Signature Components
|
|
165
|
+
|
|
166
|
+
### Send Button (the black/white circle)
|
|
167
|
+
|
|
168
|
+
The single most recognizable ChatGPT component — a 32dp circle, ink-on-canvas inverse, single up-arrow. Becomes a square "stop" during streaming.
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
import androidx.compose.animation.core.animateFloatAsState
|
|
172
|
+
import androidx.compose.animation.core.spring
|
|
173
|
+
import androidx.compose.foundation.background
|
|
174
|
+
import androidx.compose.foundation.clickable
|
|
175
|
+
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
176
|
+
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
|
177
|
+
import androidx.compose.foundation.isSystemInDarkTheme
|
|
178
|
+
import androidx.compose.foundation.layout.size
|
|
179
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
180
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
181
|
+
import androidx.compose.material.icons.Icons
|
|
182
|
+
import androidx.compose.material.icons.filled.ArrowUpward
|
|
183
|
+
import androidx.compose.material.icons.filled.Stop
|
|
184
|
+
import androidx.compose.material3.Icon
|
|
185
|
+
import androidx.compose.runtime.*
|
|
186
|
+
import androidx.compose.ui.Modifier
|
|
187
|
+
import androidx.compose.ui.draw.clip
|
|
188
|
+
import androidx.compose.ui.draw.scale
|
|
189
|
+
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
190
|
+
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
191
|
+
import androidx.compose.ui.unit.dp
|
|
192
|
+
|
|
193
|
+
@Composable
|
|
194
|
+
fun SendButton(
|
|
195
|
+
enabled: Boolean,
|
|
196
|
+
generating: Boolean,
|
|
197
|
+
onClick: () -> Unit,
|
|
198
|
+
modifier: Modifier = Modifier,
|
|
199
|
+
) {
|
|
200
|
+
val dark = isSystemInDarkTheme()
|
|
201
|
+
val interaction = remember { MutableInteractionSource() }
|
|
202
|
+
val pressed by interaction.collectIsPressedAsState()
|
|
203
|
+
val scale by animateFloatAsState(
|
|
204
|
+
targetValue = if (pressed) 0.94f else 1f,
|
|
205
|
+
animationSpec = spring(dampingRatio = 0.7f, stiffness = 700f),
|
|
206
|
+
label = "sendScale",
|
|
207
|
+
)
|
|
208
|
+
val circle = when {
|
|
209
|
+
!enabled && !generating -> if (dark) GptColors.SendDisabledDk else GptColors.SendDisabled
|
|
210
|
+
dark -> GptColors.SendDark
|
|
211
|
+
else -> GptColors.SendLight
|
|
212
|
+
}
|
|
213
|
+
val iconTint = if (dark) GptColors.SendLight else GptColors.SendDark
|
|
214
|
+
val haptics = LocalHapticFeedback.current
|
|
215
|
+
|
|
216
|
+
Box(
|
|
217
|
+
modifier = modifier
|
|
218
|
+
.size(32.dp)
|
|
219
|
+
.scale(scale)
|
|
220
|
+
.clip(CircleShape)
|
|
221
|
+
.background(circle)
|
|
222
|
+
.clickable(interaction, indication = null, enabled = enabled || generating) {
|
|
223
|
+
haptics.performHapticFeedback(HapticFeedbackType.LongPress) // ≈ .impact solid
|
|
224
|
+
onClick()
|
|
225
|
+
},
|
|
226
|
+
contentAlignment = Alignment.Center,
|
|
227
|
+
) {
|
|
228
|
+
Icon(
|
|
229
|
+
imageVector = if (generating) Icons.Filled.Stop else Icons.Filled.ArrowUpward,
|
|
230
|
+
contentDescription = if (generating) "Stop generating" else "Send",
|
|
231
|
+
tint = iconTint,
|
|
232
|
+
modifier = Modifier.size(16.dp),
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### User Message Bubble (asymmetric corners)
|
|
239
|
+
|
|
240
|
+
Right-aligned, max-width ~80%, soft-gray fill, with the "pointer" corner flattened to 4dp on the bottom-trailing edge.
|
|
241
|
+
|
|
242
|
+
```kotlin
|
|
243
|
+
import androidx.compose.foundation.layout.*
|
|
244
|
+
import androidx.compose.material3.Text
|
|
245
|
+
|
|
246
|
+
private val UserBubbleShape = RoundedCornerShape(
|
|
247
|
+
topStart = 18.dp, topEnd = 18.dp, bottomStart = 18.dp, bottomEnd = 4.dp,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
@Composable
|
|
251
|
+
fun UserMessageBubble(text: String, modifier: Modifier = Modifier) {
|
|
252
|
+
val dark = isSystemInDarkTheme()
|
|
253
|
+
Row(modifier.fillMaxWidth().padding(horizontal = 16.dp)) {
|
|
254
|
+
Spacer(Modifier.weight(0.2f)) // ~80% max width
|
|
255
|
+
Box(
|
|
256
|
+
Modifier
|
|
257
|
+
.weight(0.8f, fill = false)
|
|
258
|
+
.clip(UserBubbleShape)
|
|
259
|
+
.background(if (dark) GptColors.UserBubbleDark else GptColors.UserBubbleLight)
|
|
260
|
+
.padding(horizontal = 14.dp, vertical = 10.dp),
|
|
261
|
+
) {
|
|
262
|
+
Text(
|
|
263
|
+
text,
|
|
264
|
+
style = GptText.Body,
|
|
265
|
+
color = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Assistant Message (no bubble + feedback row)
|
|
273
|
+
|
|
274
|
+
Plain markdown-rendered content flowing inline — **no bubble, no box** — with the canonical 4-icon feedback row beneath.
|
|
275
|
+
|
|
276
|
+
```kotlin
|
|
277
|
+
import androidx.compose.material.icons.filled.ContentCopy
|
|
278
|
+
import androidx.compose.material.icons.filled.Refresh
|
|
279
|
+
import androidx.compose.material.icons.filled.ThumbDown
|
|
280
|
+
import androidx.compose.material.icons.filled.ThumbUp
|
|
281
|
+
import androidx.compose.material.icons.filled.AutoAwesome
|
|
282
|
+
import androidx.compose.ui.graphics.vector.ImageVector
|
|
283
|
+
|
|
284
|
+
@Composable
|
|
285
|
+
fun AssistantMessage(
|
|
286
|
+
markdown: String,
|
|
287
|
+
onRegenerate: () -> Unit,
|
|
288
|
+
onCopy: () -> Unit,
|
|
289
|
+
onThumbUp: () -> Unit,
|
|
290
|
+
onThumbDown: () -> Unit,
|
|
291
|
+
modifier: Modifier = Modifier,
|
|
292
|
+
) {
|
|
293
|
+
val dark = isSystemInDarkTheme()
|
|
294
|
+
val textColor = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary
|
|
295
|
+
Column(
|
|
296
|
+
modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
|
|
297
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
298
|
+
) {
|
|
299
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
300
|
+
Icon(Icons.Filled.AutoAwesome, null, tint = textColor, modifier = Modifier.size(24.dp))
|
|
301
|
+
// Replace with a real markdown renderer (Markwon / compose-markdown):
|
|
302
|
+
// renders H1/H2/H3, lists, inline code, code blocks, tables, LaTeX.
|
|
303
|
+
Text(markdown, style = GptText.Body, color = textColor)
|
|
304
|
+
}
|
|
305
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(start = 32.dp)) {
|
|
306
|
+
FeedbackIcon(Icons.Filled.Refresh, "Regenerate", onRegenerate)
|
|
307
|
+
FeedbackIcon(Icons.Filled.ContentCopy, "Copy", onCopy)
|
|
308
|
+
FeedbackIcon(Icons.Filled.ThumbUp, "Good response", onThumbUp)
|
|
309
|
+
FeedbackIcon(Icons.Filled.ThumbDown, "Bad response", onThumbDown)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@Composable
|
|
315
|
+
private fun FeedbackIcon(icon: ImageVector, label: String, onClick: () -> Unit) {
|
|
316
|
+
val dark = isSystemInDarkTheme()
|
|
317
|
+
val interaction = remember { MutableInteractionSource() }
|
|
318
|
+
val pressed by interaction.collectIsPressedAsState()
|
|
319
|
+
val scale by animateFloatAsState(if (pressed) 1.15f else 1f, label = "thumbScale")
|
|
320
|
+
val haptics = LocalHapticFeedback.current
|
|
321
|
+
Box(
|
|
322
|
+
Modifier
|
|
323
|
+
.size(32.dp) // tight per iOS spec — see §8 (bump to 48dp for strict a11y)
|
|
324
|
+
.scale(scale)
|
|
325
|
+
.clip(CircleShape)
|
|
326
|
+
.clickable(interaction, indication = null) {
|
|
327
|
+
haptics.performHapticFeedback(HapticFeedbackType.TextHandleMove) // ≈ .impact soft
|
|
328
|
+
onClick()
|
|
329
|
+
},
|
|
330
|
+
contentAlignment = Alignment.Center,
|
|
331
|
+
) {
|
|
332
|
+
Icon(icon, contentDescription = label,
|
|
333
|
+
tint = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary,
|
|
334
|
+
modifier = Modifier.size(16.dp))
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Code Block (with Copy button)
|
|
340
|
+
|
|
341
|
+
```kotlin
|
|
342
|
+
import androidx.compose.foundation.border
|
|
343
|
+
import androidx.compose.foundation.horizontalScroll
|
|
344
|
+
import androidx.compose.foundation.rememberScrollState
|
|
345
|
+
import androidx.compose.material.icons.filled.Check
|
|
346
|
+
import kotlinx.coroutines.delay
|
|
347
|
+
|
|
348
|
+
@Composable
|
|
349
|
+
fun CodeBlock(language: String, code: String, modifier: Modifier = Modifier) {
|
|
350
|
+
val dark = isSystemInDarkTheme()
|
|
351
|
+
val bg = if (dark) GptColors.CodeBlockDark else GptColors.CodeBlockLight
|
|
352
|
+
val border = if (dark) GptColors.CodeBorderDark else GptColors.CodeBorderLight
|
|
353
|
+
val secondary = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary
|
|
354
|
+
var copied by remember { mutableStateOf(false) }
|
|
355
|
+
val clipboard = androidx.compose.ui.platform.LocalClipboardManager.current
|
|
356
|
+
|
|
357
|
+
LaunchedEffect(copied) { if (copied) { delay(1000); copied = false } }
|
|
358
|
+
|
|
359
|
+
Column(
|
|
360
|
+
modifier
|
|
361
|
+
.fillMaxWidth()
|
|
362
|
+
.padding(horizontal = 16.dp)
|
|
363
|
+
.clip(RoundedCornerShape(8.dp))
|
|
364
|
+
.background(bg)
|
|
365
|
+
.border(1.dp, border, RoundedCornerShape(8.dp)),
|
|
366
|
+
) {
|
|
367
|
+
Row(
|
|
368
|
+
Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
|
|
369
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
370
|
+
) {
|
|
371
|
+
Text(language, style = GptText.SidebarSection, color = secondary)
|
|
372
|
+
Spacer(Modifier.weight(1f))
|
|
373
|
+
Row(
|
|
374
|
+
Modifier.clip(RoundedCornerShape(6.dp)).clickable {
|
|
375
|
+
clipboard.setText(androidx.compose.ui.text.AnnotatedString(code)); copied = true
|
|
376
|
+
}.padding(4.dp),
|
|
377
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
378
|
+
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
379
|
+
) {
|
|
380
|
+
Icon(if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy, null,
|
|
381
|
+
tint = secondary, modifier = Modifier.size(12.dp))
|
|
382
|
+
Text(if (copied) "Copied!" else "Copy", style = GptText.Button, color = secondary)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
Box(Modifier.fillMaxWidth().height(1.dp).background(border))
|
|
386
|
+
Box(Modifier.horizontalScroll(rememberScrollState()).padding(12.dp)) {
|
|
387
|
+
Text(code, style = GptText.CodeBlock,
|
|
388
|
+
color = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Model Selector Chip
|
|
395
|
+
|
|
396
|
+
```kotlin
|
|
397
|
+
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
398
|
+
|
|
399
|
+
@Composable
|
|
400
|
+
fun ModelSelectorChip(modelName: String, onTap: () -> Unit, modifier: Modifier = Modifier) {
|
|
401
|
+
val dark = isSystemInDarkTheme()
|
|
402
|
+
val text = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary
|
|
403
|
+
val border = if (dark) GptColors.DividerDark else GptColors.Divider
|
|
404
|
+
Row(
|
|
405
|
+
modifier
|
|
406
|
+
.clip(CircleShape)
|
|
407
|
+
.border(1.dp, border, CircleShape)
|
|
408
|
+
.clickable { onTap() }
|
|
409
|
+
.padding(horizontal = 10.dp, vertical = 6.dp),
|
|
410
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
411
|
+
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
412
|
+
) {
|
|
413
|
+
Icon(Icons.Filled.AutoAwesome, null, tint = text, modifier = Modifier.size(14.dp))
|
|
414
|
+
Text(modelName, style = GptText.ModelChip, color = text)
|
|
415
|
+
Icon(Icons.Filled.KeyboardArrowDown, "Choose model",
|
|
416
|
+
tint = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary,
|
|
417
|
+
modifier = Modifier.size(10.dp))
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Message Composer
|
|
423
|
+
|
|
424
|
+
Bottom-docked rounded container. When empty: paperclip + globe + mic. When text entered: the send button replaces the trailing icons.
|
|
425
|
+
|
|
426
|
+
```kotlin
|
|
427
|
+
import androidx.compose.foundation.text.BasicTextField
|
|
428
|
+
import androidx.compose.material.icons.filled.Add
|
|
429
|
+
import androidx.compose.material.icons.filled.Mic
|
|
430
|
+
import androidx.compose.material.icons.filled.Public
|
|
431
|
+
import androidx.compose.ui.text.input.TextFieldValue
|
|
432
|
+
|
|
433
|
+
@Composable
|
|
434
|
+
fun Composer(
|
|
435
|
+
value: TextFieldValue,
|
|
436
|
+
onValueChange: (TextFieldValue) -> Unit,
|
|
437
|
+
onSend: () -> Unit,
|
|
438
|
+
onVoice: () -> Unit,
|
|
439
|
+
onAttach: () -> Unit,
|
|
440
|
+
onWebSearch: () -> Unit,
|
|
441
|
+
modifier: Modifier = Modifier,
|
|
442
|
+
) {
|
|
443
|
+
val dark = isSystemInDarkTheme()
|
|
444
|
+
val empty = value.text.isBlank()
|
|
445
|
+
val border = if (dark) GptColors.DividerDark else GptColors.Divider
|
|
446
|
+
val secondary = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary
|
|
447
|
+
|
|
448
|
+
Row(
|
|
449
|
+
modifier
|
|
450
|
+
.fillMaxWidth()
|
|
451
|
+
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
452
|
+
.clip(RoundedCornerShape(24.dp))
|
|
453
|
+
.background(if (dark) GptColors.DarkCanvas else GptColors.Canvas)
|
|
454
|
+
.border(1.dp, border, RoundedCornerShape(24.dp))
|
|
455
|
+
.padding(horizontal = 8.dp, vertical = 8.dp),
|
|
456
|
+
verticalAlignment = Alignment.Bottom,
|
|
457
|
+
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
458
|
+
) {
|
|
459
|
+
IconHit(Icons.Filled.Add, "Attach", secondary, onAttach)
|
|
460
|
+
Box(Modifier.weight(1f).padding(vertical = 10.dp)) {
|
|
461
|
+
if (value.text.isEmpty()) {
|
|
462
|
+
Text("Message ChatGPT…", style = GptText.Placeholder, color = secondary)
|
|
463
|
+
}
|
|
464
|
+
BasicTextField(
|
|
465
|
+
value = value, onValueChange = onValueChange,
|
|
466
|
+
textStyle = GptText.Body.copy(
|
|
467
|
+
color = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary),
|
|
468
|
+
maxLines = 6,
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
if (empty) {
|
|
472
|
+
IconHit(Icons.Filled.Public, "Web search", secondary, onWebSearch)
|
|
473
|
+
Box(
|
|
474
|
+
Modifier.size(32.dp).clip(CircleShape).border(1.dp, border, CircleShape)
|
|
475
|
+
.clickable { onVoice() },
|
|
476
|
+
contentAlignment = Alignment.Center,
|
|
477
|
+
) { Icon(Icons.Filled.Mic, "Voice", tint = secondary, modifier = Modifier.size(18.dp)) }
|
|
478
|
+
} else {
|
|
479
|
+
SendButton(enabled = true, generating = false, onClick = onSend)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@Composable
|
|
485
|
+
private fun IconHit(icon: ImageVector, label: String, tint: Color, onClick: () -> Unit) {
|
|
486
|
+
Box(
|
|
487
|
+
Modifier.size(40.dp).clip(RoundedCornerShape(8.dp)).clickable { onClick() },
|
|
488
|
+
contentAlignment = Alignment.Center,
|
|
489
|
+
) { Icon(icon, contentDescription = label, tint = tint, modifier = Modifier.size(20.dp)) }
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Typing Indicator (3-dot pulse)
|
|
494
|
+
|
|
495
|
+
```kotlin
|
|
496
|
+
import androidx.compose.animation.core.RepeatMode
|
|
497
|
+
import androidx.compose.animation.core.infiniteRepeatable
|
|
498
|
+
import androidx.compose.animation.core.rememberInfiniteTransition
|
|
499
|
+
import androidx.compose.animation.core.tween
|
|
500
|
+
import androidx.compose.ui.draw.alpha
|
|
501
|
+
|
|
502
|
+
@Composable
|
|
503
|
+
fun TypingIndicator(modifier: Modifier = Modifier) {
|
|
504
|
+
val dark = isSystemInDarkTheme()
|
|
505
|
+
val color = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary
|
|
506
|
+
val t = rememberInfiniteTransition(label = "typing")
|
|
507
|
+
Row(modifier, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
508
|
+
repeat(3) { i ->
|
|
509
|
+
val a by t.animateFloat(
|
|
510
|
+
initialValue = 0.3f, targetValue = 1f,
|
|
511
|
+
animationSpec = infiniteRepeatable(
|
|
512
|
+
tween(600, delayMillis = i * 200), RepeatMode.Reverse),
|
|
513
|
+
label = "dot$i",
|
|
514
|
+
)
|
|
515
|
+
Box(Modifier.size(8.dp).alpha(a).clip(CircleShape).background(color))
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## 4. Voice Mode (the distinctive system)
|
|
522
|
+
|
|
523
|
+
ChatGPT has no color extraction; its flagship dynamic moment is **voice mode** — a full-screen pulsing blue gradient sphere that morphs as you speak. Port the SwiftUI radial-gradient sphere with an `Animatable` scale pulse plus a subtle organic deformation. This is the product's most-recognized animation.
|
|
524
|
+
|
|
525
|
+
```kotlin
|
|
526
|
+
import androidx.compose.animation.core.Animatable
|
|
527
|
+
import androidx.compose.animation.core.LinearEasing
|
|
528
|
+
import androidx.compose.foundation.Canvas
|
|
529
|
+
import androidx.compose.material.icons.filled.Close
|
|
530
|
+
import androidx.compose.ui.geometry.Offset
|
|
531
|
+
import androidx.compose.ui.graphics.Brush
|
|
532
|
+
import kotlin.math.sin
|
|
533
|
+
|
|
534
|
+
@Composable
|
|
535
|
+
fun VoiceModeOverlay(onEnd: () -> Unit) {
|
|
536
|
+
val haptics = LocalHapticFeedback.current
|
|
537
|
+
val pulse = remember { Animatable(1f) }
|
|
538
|
+
val morph = rememberInfiniteTransition(label = "morph")
|
|
539
|
+
val wobble by morph.animateFloat(
|
|
540
|
+
initialValue = 0f, targetValue = (2 * Math.PI).toFloat(),
|
|
541
|
+
animationSpec = infiniteRepeatable(tween(3000, easing = LinearEasing)),
|
|
542
|
+
label = "wobble",
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
LaunchedEffect(Unit) {
|
|
546
|
+
haptics.performHapticFeedback(HapticFeedbackType.LongPress) // ≈ .impact solid on entry
|
|
547
|
+
// Continuous organic pulse: scale 0.95 ↔ 1.05 over 2s ease-in-out
|
|
548
|
+
while (true) {
|
|
549
|
+
pulse.animateTo(1.05f, tween(2000))
|
|
550
|
+
pulse.animateTo(0.95f, tween(2000))
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
Box(Modifier.fillMaxSize().background(Color.Black), contentAlignment = Alignment.Center) {
|
|
555
|
+
Canvas(Modifier.size(280.dp).scale(pulse.value)) {
|
|
556
|
+
// Perlin-ish deformation: radius modulated by a couple of sine terms
|
|
557
|
+
val baseR = size.minDimension / 2f
|
|
558
|
+
val r = baseR * (1f + 0.03f * sin(wobble) + 0.02f * sin(wobble * 2.3f))
|
|
559
|
+
drawCircle(
|
|
560
|
+
brush = Brush.radialGradient(
|
|
561
|
+
colors = listOf(GptColors.VoiceBlue1, GptColors.VoiceBlue2, GptColors.VoiceBlue3),
|
|
562
|
+
center = center,
|
|
563
|
+
radius = r,
|
|
564
|
+
),
|
|
565
|
+
radius = r,
|
|
566
|
+
center = center,
|
|
567
|
+
)
|
|
568
|
+
// Glow: a soft larger translucent ring (Android has no blur shadow on Canvas)
|
|
569
|
+
drawCircle(
|
|
570
|
+
color = GptColors.VoiceBlue1.copy(alpha = 0.25f),
|
|
571
|
+
radius = r * 1.25f,
|
|
572
|
+
center = center,
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
Column(
|
|
576
|
+
Modifier.align(Alignment.BottomCenter).fillMaxWidth().padding(bottom = 40.dp),
|
|
577
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
578
|
+
verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
579
|
+
) {
|
|
580
|
+
Text("ChatGPT is listening…", style = GptText.Meta, color = Color.White.copy(alpha = 0.7f))
|
|
581
|
+
Row(
|
|
582
|
+
Modifier.fillMaxWidth().padding(horizontal = 24.dp),
|
|
583
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
584
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
585
|
+
) {
|
|
586
|
+
Text("Mute", style = GptText.Button, color = Color.White,
|
|
587
|
+
modifier = Modifier.clickable { /* toggle mute */ })
|
|
588
|
+
Box(
|
|
589
|
+
Modifier.size(44.dp).clip(CircleShape).background(Color.White.copy(alpha = 0.2f))
|
|
590
|
+
.clickable { onEnd() },
|
|
591
|
+
contentAlignment = Alignment.Center,
|
|
592
|
+
) { Icon(Icons.Filled.Close, "End voice mode", tint = Color.White,
|
|
593
|
+
modifier = Modifier.size(18.dp)) }
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Present this as a full-screen destination with an `enter = scaleIn(initialScale = 0.9f) + fadeIn(tween(400))` transition. Drive an `AVAudioSession`-equivalent via `AudioManager`/`MediaRecorder`; request the `RECORD_AUDIO` runtime permission before entering. It is portrait-locked and ignores window insets (full-bleed sphere).
|
|
601
|
+
|
|
602
|
+
## 5. Navigation
|
|
603
|
+
|
|
604
|
+
ChatGPT has **no bottom tab bar**. The conversation history is a left drawer — use Material 3 `ModalNavigationDrawer` (edge-swipe with `spring(dampingRatio = 0.8f)` on release). The chat "top nav" is a transparent 52dp row: sidebar toggle (leading) + model chip (center-leading) + "New chat" (trailing). The sidebar groups chats by time (Today / Yesterday / Previous 7 Days / Previous 30 Days / Month Year) and is darker than the canvas — the signature treatment.
|
|
605
|
+
|
|
606
|
+
```kotlin
|
|
607
|
+
import androidx.compose.material.icons.filled.Edit
|
|
608
|
+
import androidx.compose.material.icons.filled.Menu
|
|
609
|
+
import androidx.compose.material.icons.filled.Search
|
|
610
|
+
import androidx.compose.material3.*
|
|
611
|
+
|
|
612
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
613
|
+
@Composable
|
|
614
|
+
fun GptScaffold(
|
|
615
|
+
drawerState: DrawerState,
|
|
616
|
+
sections: List<Pair<String, List<String>>>, // section title to chat titles
|
|
617
|
+
onNewChat: () -> Unit,
|
|
618
|
+
chat: @Composable () -> Unit,
|
|
619
|
+
) {
|
|
620
|
+
val dark = isSystemInDarkTheme()
|
|
621
|
+
ModalNavigationDrawer(
|
|
622
|
+
drawerState = drawerState,
|
|
623
|
+
drawerContent = {
|
|
624
|
+
ModalDrawerSheet(
|
|
625
|
+
drawerContainerColor = if (dark) GptColors.SidebarDark else GptColors.SidebarLight,
|
|
626
|
+
modifier = Modifier.width(300.dp),
|
|
627
|
+
) {
|
|
628
|
+
Column {
|
|
629
|
+
SidebarButton(Icons.Filled.Edit, "New chat", onNewChat)
|
|
630
|
+
SidebarButton(Icons.Filled.Search, "Search chats") {}
|
|
631
|
+
LazyColumn {
|
|
632
|
+
sections.forEach { (title, chats) ->
|
|
633
|
+
item {
|
|
634
|
+
Text(title, style = GptText.SidebarSection,
|
|
635
|
+
color = if (dark) GptColors.DarkTextSecondary else GptColors.TextSecondary,
|
|
636
|
+
modifier = Modifier.padding(start = 12.dp, top = 16.dp, bottom = 4.dp))
|
|
637
|
+
}
|
|
638
|
+
items(chats) { c ->
|
|
639
|
+
Box(
|
|
640
|
+
Modifier.fillMaxWidth().height(40.dp)
|
|
641
|
+
.padding(horizontal = 12.dp),
|
|
642
|
+
contentAlignment = Alignment.CenterStart,
|
|
643
|
+
) {
|
|
644
|
+
Text(c, style = GptText.SidebarTitle, maxLines = 1,
|
|
645
|
+
color = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary)
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
) {
|
|
654
|
+
Scaffold(
|
|
655
|
+
containerColor = if (dark) GptColors.DarkCanvas else GptColors.Canvas,
|
|
656
|
+
topBar = {
|
|
657
|
+
Row(
|
|
658
|
+
Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
|
|
659
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
660
|
+
) {
|
|
661
|
+
Icon(Icons.Filled.Menu, "Open conversations",
|
|
662
|
+
tint = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary,
|
|
663
|
+
modifier = Modifier.size(24.dp))
|
|
664
|
+
Spacer(Modifier.width(8.dp))
|
|
665
|
+
ModelSelectorChip("GPT-4o", onTap = {})
|
|
666
|
+
Spacer(Modifier.weight(1f))
|
|
667
|
+
Icon(Icons.Filled.Edit, "New chat",
|
|
668
|
+
tint = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary,
|
|
669
|
+
modifier = Modifier.size(24.dp).clickable { onNewChat() })
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
) { padding -> Box(Modifier.padding(padding)) { chat() } }
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
@Composable
|
|
677
|
+
private fun SidebarButton(icon: ImageVector, label: String, onClick: () -> Unit) {
|
|
678
|
+
val dark = isSystemInDarkTheme()
|
|
679
|
+
Row(
|
|
680
|
+
Modifier.padding(12.dp).fillMaxWidth().clip(RoundedCornerShape(8.dp))
|
|
681
|
+
.background(if (dark) GptColors.SidebarActiveDk else GptColors.SidebarActive)
|
|
682
|
+
.clickable { onClick() }.padding(horizontal = 12.dp, vertical = 10.dp),
|
|
683
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
684
|
+
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
|
685
|
+
) {
|
|
686
|
+
Icon(icon, null, tint = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary,
|
|
687
|
+
modifier = Modifier.size(18.dp))
|
|
688
|
+
Text(label, style = GptText.Button,
|
|
689
|
+
color = if (dark) GptColors.DarkTextPrimary else GptColors.TextPrimary)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Bottom sheets (model picker, attachment sheet) use Material 3 `ModalBottomSheet`. Android has no live blur; ChatGPT's sheets use a plain dim scrim anyway, so use `scrimColor` directly — nothing to approximate.
|
|
695
|
+
|
|
696
|
+
## 6. Motion
|
|
697
|
+
|
|
698
|
+
ChatGPT is restrained and editorial — streaming text has **no animation** (append tokens character-by-character).
|
|
699
|
+
|
|
700
|
+
| Moment | Compose recipe |
|
|
701
|
+
|--------|----------------|
|
|
702
|
+
| Message send | user bubble `slideInVertically` + `fadeIn` 250ms ease-out; send button scale 0.9 → 1.0 `spring` |
|
|
703
|
+
| Typing indicator | `rememberInfiniteTransition` 3 dots, alpha 0.3 ↔ 1.0 over 600ms, staggered 200ms |
|
|
704
|
+
| Assistant streaming | none — append to the string as tokens arrive |
|
|
705
|
+
| Regenerate | response `fadeOut` 200ms, new response streams in |
|
|
706
|
+
| Voice mode entry | `scaleIn(0.9f) + fadeIn(tween(400))` spring; `HapticFeedbackType.LongPress` (.impact solid) |
|
|
707
|
+
| Voice sphere | `Animatable` scale 0.95 ↔ 1.05 over 2s + sine deformation on the `Canvas` radius |
|
|
708
|
+
| Drawer slide | `ModalNavigationDrawer` edge-swipe; `spring(dampingRatio = 0.8f)` on release |
|
|
709
|
+
| Copy button | swap icon + label to "Copied!" for ~1s then revert |
|
|
710
|
+
| Thumbs feedback | fill icon + `scale 1.0 → 1.15 → 1.0` `spring`; `HapticFeedbackType.TextHandleMove` (.soft) |
|
|
711
|
+
|
|
712
|
+
```kotlin
|
|
713
|
+
// Message-send bubble entrance
|
|
714
|
+
AnimatedVisibility(
|
|
715
|
+
visible = true,
|
|
716
|
+
enter = slideInVertically(tween(250)) { it / 2 } + fadeIn(tween(250)),
|
|
717
|
+
) { UserMessageBubble(text) }
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Honor **Reduce Motion**: when `Settings.Global.ANIMATOR_DURATION_SCALE == 0f`, render the voice sphere static (no pulse/morph), the typing indicator as 3 static dots with a "Generating…" label, and skip the send-button scale — keep haptics. For richer haptics use `LocalView.current.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)` (API 30+) or a `Vibrator` `VibrationEffect.createOneShot(8, ...)` to approximate iOS's `.soft` thumbs feedback.
|
|
721
|
+
|
|
722
|
+
## 7. Icons
|
|
723
|
+
|
|
724
|
+
ChatGPT uses simple glyphs; the closest first-party set is `androidx.compose.material:material-icons-extended`. The **ChatGPT sparkle/blossom logomark** is proprietary — ship it as a vector drawable and load via `ImageVector.vectorResource(R.drawable.…)`; `Icons.Filled.AutoAwesome` is a reasonable stand-in for the assistant avatar in prototypes.
|
|
725
|
+
|
|
726
|
+
| Purpose | SF Symbol (iOS) | Material Icon (Compose) |
|
|
727
|
+
|---------|-----------------|--------------------------|
|
|
728
|
+
| Send | `arrow.up` | `Icons.Filled.ArrowUpward` |
|
|
729
|
+
| Stop generating | `stop.fill` | `Icons.Filled.Stop` |
|
|
730
|
+
| Voice (mic) | `mic.fill` / `waveform` | `Icons.Filled.Mic` |
|
|
731
|
+
| Attach (+) | `plus` | `Icons.Filled.Add` |
|
|
732
|
+
| Web search | `globe` | `Icons.Filled.Public` |
|
|
733
|
+
| New chat | `square.and.pencil` | `Icons.Filled.Edit` |
|
|
734
|
+
| Sidebar toggle | `sidebar.leading` | `Icons.Filled.Menu` |
|
|
735
|
+
| Regenerate | `arrow.triangle.2.circlepath` | `Icons.Filled.Refresh` |
|
|
736
|
+
| Copy | `doc.on.doc` | `Icons.Filled.ContentCopy` |
|
|
737
|
+
| Thumbs up | `hand.thumbsup` / `.fill` | `Icons.Filled.ThumbUp` |
|
|
738
|
+
| Thumbs down | `hand.thumbsdown` / `.fill` | `Icons.Filled.ThumbDown` |
|
|
739
|
+
| Sparkle (assistant avatar) | `sparkles` | `Icons.Filled.AutoAwesome` (or custom logomark) |
|
|
740
|
+
| Model chip chevron | `chevron.down` | `Icons.Filled.KeyboardArrowDown` |
|
|
741
|
+
| Close (X) | `xmark` | `Icons.Filled.Close` |
|
|
742
|
+
| Search | `magnifyingglass` | `Icons.Filled.Search` |
|
|
743
|
+
| Copied confirmation | `checkmark` | `Icons.Filled.Check` |
|
|
744
|
+
|
|
745
|
+
## 8. Minimum SDK & Accessibility Notes
|
|
746
|
+
|
|
747
|
+
- **minSdk 24** (Compose floor is 21; modern motion + audio comfortable at 24). `compileSdk 34+`, Compose BOM `2024.09+`, Kotlin `2.0+`. Söhne requires a commercial license from Klim Type Foundry — Inter (OFL) or Roboto (system) are acceptable free fallbacks.
|
|
748
|
+
- **Edge-to-edge**: call `enableEdgeToEdge()` in the `Activity`; system-bar icon contrast must follow the theme (dark icons on the light canvas, light icons on `#212121`). The composer rises with the IME via `Modifier.imePadding()`; voice mode deliberately ignores insets (full-bleed sphere).
|
|
749
|
+
- **Font scaling**: `sp` honors the user's font scale automatically — keep it on chat body, markdown headings, user-bubble text, code blocks (code stays monospace at XL). Pin layout-sensitive text (model chip, sidebar section titles, timestamps, code-block language label) by deriving from `dp` or wrapping in `CompositionLocalProvider(LocalDensity provides Density(density, fontScale = 1f))`.
|
|
750
|
+
- **TalkBack**: label user turns "You: {content}" and assistant turns "ChatGPT: {content}"; announce voice mode as "Voice conversation active, ChatGPT is listening". Expose the feedback row via `Modifier.semantics` custom actions ("Regenerate", "Copy", "Good response", "Bad response"). Every icon-only control needs a `contentDescription`.
|
|
751
|
+
- **Touch targets**: Material guidance is 48.dp minimum. The send/voice 32dp circles sit in 40–48dp composer hit areas; the **feedback-row icons are a tight 32dp per the iOS spec** — for strict accessibility compliance bump their hit area to 48dp via padding while keeping the 16dp glyph.
|
|
752
|
+
- **Contrast**: `#0D0D0D` on `#FFFFFF` and `#ECECEC` on `#212121` exceed WCAG AA at all sizes. `#676767` secondary on white meets AA at 14sp+; in High Contrast mode boost it toward `#3E3E3E`. The link blue `#2A7FFF` on white passes AA for text.
|
|
753
|
+
- **Dynamic color**: do **not** enable Material You `dynamicColorScheme()` — ChatGPT's identity is the deliberate monochrome (white/charcoal canvas, ink text, blue only on links and the voice sphere) regardless of wallpaper. (Material You suits Google/Material-first apps; ChatGPT is a strong-brand exception.) Unlike most strong-brand apps, ChatGPT *does* support both light and dark equally — switch on `isSystemInDarkTheme()`, never tint from the wallpaper.
|