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,668 @@
|
|
|
1
|
+
# YouTube (iOS) — Jetpack Compose Implementation Guide
|
|
2
|
+
|
|
3
|
+
Companion to [DESIGN.md](DESIGN.md) — the framework-neutral spec. This file ports YouTube's visual language to **Android with Jetpack Compose (Material 3)**: a color token object, a `Typography` set, paste-ready `@Composable`s, the 16:9 video card, the draggable mini-player, the Shorts rail, motion, and haptics.
|
|
4
|
+
|
|
5
|
+
> Why a Compose guide for an iOS-referenced app? The DESIGN.md tokens are platform-neutral — and YouTube is itself a Google product whose Material roots already show. This file keeps the *visual* identity (the 16:9 hard-edge thumbnail as the atomic unit, single-red brand accent reserved for Subscribe + brand marks, the near-black `#0F0F0F` dark canvas, the persistent draggable mini-player) while making everything idiomatic Android — `NavigationBar` instead of a UITabBar, `AnchoredDraggable` for the mini-player, a translucent `Surface` (no live blur), `sp`/`dp` instead of `pt`. Light-first with a near-black dark mode; both are wired below.
|
|
6
|
+
|
|
7
|
+
Assumes Compose BOM `2024.09+`, Kotlin `2.0+`, `minSdk 24`, Material 3, and [Coil](https://coil-kt.github.io/coil/) `2.6+` for thumbnails, channel avatars, and Shorts audio discs. No color extraction — YouTube's palette is fixed brand chrome.
|
|
8
|
+
|
|
9
|
+
## 1. Color Tokens
|
|
10
|
+
|
|
11
|
+
```kotlin
|
|
12
|
+
// ui/theme/YouTubeColors.kt
|
|
13
|
+
import androidx.compose.ui.graphics.Color
|
|
14
|
+
|
|
15
|
+
object YouTubeColors {
|
|
16
|
+
// Brand
|
|
17
|
+
val Red = Color(0xFFFF0000) // Subscribe + brand marks ONLY
|
|
18
|
+
val RedPressed = Color(0xFFCC0000)
|
|
19
|
+
val RedHover = Color(0xFFE60000)
|
|
20
|
+
|
|
21
|
+
// Light canvas
|
|
22
|
+
val CanvasLight = Color(0xFFFFFFFF)
|
|
23
|
+
val Surface1Light = Color(0xFFF9F9F9)
|
|
24
|
+
val Surface2Light = Color(0xFFF2F2F2)
|
|
25
|
+
val DividerLight = Color(0xFFE5E5E5)
|
|
26
|
+
|
|
27
|
+
// Dark canvas
|
|
28
|
+
val CanvasDark = Color(0xFF0F0F0F) // signature near-black, NOT pure black
|
|
29
|
+
val Surface1Dark = Color(0xFF1F1F1F)
|
|
30
|
+
val Surface2Dark = Color(0xFF272727)
|
|
31
|
+
val Surface3Dark = Color(0xFF3F3F3F)
|
|
32
|
+
val DividerDark = Color(0xFF303030)
|
|
33
|
+
|
|
34
|
+
// Text (light)
|
|
35
|
+
val TextPrimaryLight = Color(0xFF0F0F0F)
|
|
36
|
+
val TextSecondaryLight = Color(0xFF606060)
|
|
37
|
+
val TextTertiaryLight = Color(0xFF909090)
|
|
38
|
+
val TextDisabledLight = Color(0xFFC0C0C0)
|
|
39
|
+
|
|
40
|
+
// Text (dark)
|
|
41
|
+
val TextPrimaryDark = Color(0xFFFFFFFF)
|
|
42
|
+
val TextSecondaryDark = Color(0xFFAAAAAA)
|
|
43
|
+
val TextTertiaryDark = Color(0xFF717171)
|
|
44
|
+
|
|
45
|
+
// Semantic
|
|
46
|
+
val InfoBlue = Color(0xFF3EA6FF) // links, "Show more", send-arrow active
|
|
47
|
+
val WarningYellow = Color(0xFFFFC400)
|
|
48
|
+
val Scrim = Color(0xBF000000) // duration tag bg ~rgba(0,0,0,0.75)
|
|
49
|
+
val ShortsTrack = Color(0x4DFFFFFF) // rgba(255,255,255,0.3)
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
YouTube is light-first with a near-black dark mode. Provide both schemes; `primary` carries the brand red (used by the Subscribe button only — keep it off generic components). The **active tab tint is achromatic** (`#0F0F0F`/`#FFFFFF`), never red.
|
|
54
|
+
|
|
55
|
+
```kotlin
|
|
56
|
+
// ui/theme/Theme.kt
|
|
57
|
+
import androidx.compose.material3.darkColorScheme
|
|
58
|
+
import androidx.compose.material3.lightColorScheme
|
|
59
|
+
import androidx.compose.material3.MaterialTheme
|
|
60
|
+
import androidx.compose.runtime.Composable
|
|
61
|
+
import androidx.compose.foundation.isSystemInDarkTheme
|
|
62
|
+
|
|
63
|
+
private val YouTubeLight = lightColorScheme(
|
|
64
|
+
primary = YouTubeColors.Red,
|
|
65
|
+
onPrimary = Color.White,
|
|
66
|
+
background = YouTubeColors.CanvasLight,
|
|
67
|
+
onBackground = YouTubeColors.TextPrimaryLight,
|
|
68
|
+
surface = YouTubeColors.CanvasLight,
|
|
69
|
+
onSurface = YouTubeColors.TextPrimaryLight,
|
|
70
|
+
surfaceVariant = YouTubeColors.Surface2Light,
|
|
71
|
+
outline = YouTubeColors.DividerLight,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
private val YouTubeDark = darkColorScheme(
|
|
75
|
+
primary = YouTubeColors.Red,
|
|
76
|
+
onPrimary = Color.White,
|
|
77
|
+
background = YouTubeColors.CanvasDark,
|
|
78
|
+
onBackground = YouTubeColors.TextPrimaryDark,
|
|
79
|
+
surface = YouTubeColors.CanvasDark,
|
|
80
|
+
onSurface = YouTubeColors.TextPrimaryDark,
|
|
81
|
+
surfaceVariant = YouTubeColors.Surface2Dark,
|
|
82
|
+
outline = YouTubeColors.DividerDark,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@Composable
|
|
86
|
+
fun YouTubeTheme(
|
|
87
|
+
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
88
|
+
content: @Composable () -> Unit,
|
|
89
|
+
) = MaterialTheme(
|
|
90
|
+
colorScheme = if (darkTheme) YouTubeDark else YouTubeLight,
|
|
91
|
+
typography = YouTubeTypography,
|
|
92
|
+
content = content,
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 2. Typography
|
|
97
|
+
|
|
98
|
+
YouTube pairs **YouTube Sans** (proprietary display, 2017) with **Roboto** (body). Roboto *is* the Android system font (`FontFamily.Default`) — use it directly for body. Drop YouTube Sans TTFs in `res/font/` for display moments; fall back to Roboto if absent. Keep the two families in their lanes.
|
|
99
|
+
|
|
100
|
+
```kotlin
|
|
101
|
+
// ui/theme/YouTubeType.kt
|
|
102
|
+
import androidx.compose.material3.Typography
|
|
103
|
+
import androidx.compose.ui.text.TextStyle
|
|
104
|
+
import androidx.compose.ui.text.font.Font
|
|
105
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
106
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
107
|
+
import androidx.compose.ui.unit.sp
|
|
108
|
+
|
|
109
|
+
val YouTubeSans = FontFamily(
|
|
110
|
+
Font(R.font.youtube_sans_medium, FontWeight.Medium),
|
|
111
|
+
Font(R.font.youtube_sans_bold, FontWeight.Bold),
|
|
112
|
+
)
|
|
113
|
+
val Roboto = FontFamily.Default // Roboto is Android's system face — exact body match
|
|
114
|
+
|
|
115
|
+
// Named ramp — mirrors DESIGN.md §3 exactly (pt → sp 1:1)
|
|
116
|
+
object YouTubeText {
|
|
117
|
+
val ScreenTitle = TextStyle(YouTubeSans, fontWeight = FontWeight.Bold, fontSize = 20.sp, lineHeight = 24.sp, letterSpacing = (-0.1).sp)
|
|
118
|
+
val VideoDetailTitle = TextStyle(Roboto, fontWeight = FontWeight.SemiBold, fontSize = 18.sp, lineHeight = 23.sp, letterSpacing = (-0.1).sp)
|
|
119
|
+
val VideoTitle = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 21.sp) // most common moment, 2-line clamp
|
|
120
|
+
val ChannelName = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 18.sp)
|
|
121
|
+
val Metadata = TextStyle(Roboto, fontWeight = FontWeight.Normal, fontSize = 13.sp, lineHeight = 17.sp)
|
|
122
|
+
val CommentBody = TextStyle(Roboto, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp)
|
|
123
|
+
val CommentAuthor = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 13.sp, lineHeight = 17.sp)
|
|
124
|
+
val Body = TextStyle(Roboto, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp)
|
|
125
|
+
val Button = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 14.sp)
|
|
126
|
+
val Chip = TextStyle(Roboto, fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 14.sp)
|
|
127
|
+
val TabLabel = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 10.sp, lineHeight = 10.sp, letterSpacing = 0.1.sp)
|
|
128
|
+
val DurationTag = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 11.sp, lineHeight = 11.sp)
|
|
129
|
+
val Timestamp = TextStyle(Roboto, fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = 12.sp)
|
|
130
|
+
val LikeCount = TextStyle(Roboto, fontWeight = FontWeight.Medium, fontSize = 13.sp, lineHeight = 13.sp)
|
|
131
|
+
val ShortsCaption = TextStyle(YouTubeSans, fontWeight = FontWeight.Medium, fontSize = 15.sp, lineHeight = 20.sp)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Map onto Material 3 slots so stock components inherit the ramp
|
|
135
|
+
val YouTubeTypography = Typography(
|
|
136
|
+
headlineLarge = YouTubeText.ScreenTitle,
|
|
137
|
+
titleLarge = YouTubeText.VideoDetailTitle,
|
|
138
|
+
titleMedium = YouTubeText.VideoTitle,
|
|
139
|
+
bodyMedium = YouTubeText.Body,
|
|
140
|
+
labelSmall = YouTubeText.TabLabel,
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 3. Signature Components
|
|
145
|
+
|
|
146
|
+
### Subscribe Button (the iconic red → gray morph)
|
|
147
|
+
|
|
148
|
+
YouTube's signature button shape is an **18dp rounded rectangle** — never a full pill. On subscribe, the red morphs to gray and a bell appears, with a light haptic.
|
|
149
|
+
|
|
150
|
+
```kotlin
|
|
151
|
+
import androidx.compose.animation.AnimatedVisibility
|
|
152
|
+
import androidx.compose.animation.animateColorAsState
|
|
153
|
+
import androidx.compose.animation.core.spring
|
|
154
|
+
import androidx.compose.animation.fadeIn
|
|
155
|
+
import androidx.compose.animation.fadeOut
|
|
156
|
+
import androidx.compose.animation.slideInHorizontally
|
|
157
|
+
import androidx.compose.foundation.background
|
|
158
|
+
import androidx.compose.foundation.clickable
|
|
159
|
+
import androidx.compose.foundation.layout.*
|
|
160
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
161
|
+
import androidx.compose.material.icons.Icons
|
|
162
|
+
import androidx.compose.material.icons.filled.Notifications
|
|
163
|
+
import androidx.compose.material.icons.outlined.NotificationsNone
|
|
164
|
+
import androidx.compose.material3.Icon
|
|
165
|
+
import androidx.compose.material3.Text
|
|
166
|
+
import androidx.compose.runtime.*
|
|
167
|
+
import androidx.compose.ui.Alignment
|
|
168
|
+
import androidx.compose.ui.Modifier
|
|
169
|
+
import androidx.compose.ui.draw.clip
|
|
170
|
+
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
171
|
+
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
172
|
+
import androidx.compose.ui.unit.dp
|
|
173
|
+
|
|
174
|
+
@Composable
|
|
175
|
+
fun SubscribeButton(
|
|
176
|
+
subscribed: Boolean,
|
|
177
|
+
onToggle: () -> Unit,
|
|
178
|
+
modifier: Modifier = Modifier,
|
|
179
|
+
darkTheme: Boolean = false,
|
|
180
|
+
) {
|
|
181
|
+
val haptics = LocalHapticFeedback.current
|
|
182
|
+
val subscribedBg = if (darkTheme) YouTubeColors.Surface2Dark else YouTubeColors.Surface2Light
|
|
183
|
+
val subscribedFg = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
184
|
+
val bg by animateColorAsState(
|
|
185
|
+
targetValue = if (subscribed) subscribedBg else YouTubeColors.Red,
|
|
186
|
+
animationSpec = spring(dampingRatio = 0.8f, stiffness = 700f), // ~200ms morph
|
|
187
|
+
label = "subscribeBg",
|
|
188
|
+
)
|
|
189
|
+
var bellOn by remember { mutableStateOf(false) }
|
|
190
|
+
|
|
191
|
+
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
|
192
|
+
Box(
|
|
193
|
+
Modifier
|
|
194
|
+
.clip(RoundedCornerShape(18.dp)) // signature rounded-rect, NOT full pill
|
|
195
|
+
.background(bg)
|
|
196
|
+
.clickable {
|
|
197
|
+
haptics.performHapticFeedback(HapticFeedbackType.LongPress) // light/success
|
|
198
|
+
onToggle()
|
|
199
|
+
}
|
|
200
|
+
.padding(horizontal = 16.dp, vertical = 8.dp),
|
|
201
|
+
contentAlignment = Alignment.Center,
|
|
202
|
+
) {
|
|
203
|
+
Text(
|
|
204
|
+
if (subscribed) "Subscribed" else "Subscribe",
|
|
205
|
+
style = YouTubeText.Button,
|
|
206
|
+
color = if (subscribed) subscribedFg else Color.White,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
AnimatedVisibility(
|
|
210
|
+
visible = subscribed,
|
|
211
|
+
enter = slideInHorizontally { it } + fadeIn(),
|
|
212
|
+
exit = fadeOut(),
|
|
213
|
+
) {
|
|
214
|
+
Box(
|
|
215
|
+
Modifier
|
|
216
|
+
.padding(start = 4.dp)
|
|
217
|
+
.clip(RoundedCornerShape(18.dp))
|
|
218
|
+
.background(subscribedBg)
|
|
219
|
+
.clickable { bellOn = !bellOn; haptics.performHapticFeedback(HapticFeedbackType.LongPress) }
|
|
220
|
+
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
221
|
+
) {
|
|
222
|
+
Icon(
|
|
223
|
+
if (bellOn) Icons.Filled.Notifications else Icons.Outlined.NotificationsNone,
|
|
224
|
+
contentDescription = if (bellOn) "Notifications on" else "Notifications off",
|
|
225
|
+
tint = subscribedFg,
|
|
226
|
+
modifier = Modifier.size(16.dp),
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Video Card (16:9 hard-edge thumbnail — the atomic unit)
|
|
235
|
+
|
|
236
|
+
The thumbnail bleeds edge-to-edge with **no corner radius** — the hard edge is load-bearing. The duration tag sits absolute bottom-right; a live video shows a red LIVE pill top-left instead.
|
|
237
|
+
|
|
238
|
+
```kotlin
|
|
239
|
+
import androidx.compose.foundation.layout.aspectRatio
|
|
240
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
241
|
+
import androidx.compose.material.icons.filled.MoreVert
|
|
242
|
+
import androidx.compose.ui.layout.ContentScale
|
|
243
|
+
import androidx.compose.ui.text.style.TextOverflow
|
|
244
|
+
import coil.compose.AsyncImage
|
|
245
|
+
|
|
246
|
+
@Composable
|
|
247
|
+
fun VideoCard(
|
|
248
|
+
thumbnailUrl: String,
|
|
249
|
+
duration: String, // "12:34"
|
|
250
|
+
isLive: Boolean,
|
|
251
|
+
title: String,
|
|
252
|
+
channelAvatarUrl: String,
|
|
253
|
+
channelName: String,
|
|
254
|
+
viewCount: String, // "1.2M views"
|
|
255
|
+
uploadedAgo: String, // "3 days ago"
|
|
256
|
+
onClick: () -> Unit,
|
|
257
|
+
modifier: Modifier = Modifier,
|
|
258
|
+
) {
|
|
259
|
+
Column(modifier.fillMaxWidth().clickable(onClick = onClick)) {
|
|
260
|
+
Box {
|
|
261
|
+
AsyncImage(
|
|
262
|
+
model = thumbnailUrl,
|
|
263
|
+
contentDescription = title,
|
|
264
|
+
contentScale = ContentScale.Crop,
|
|
265
|
+
modifier = Modifier
|
|
266
|
+
.fillMaxWidth()
|
|
267
|
+
.aspectRatio(16f / 9f), // NO clip — hard edges are the signature
|
|
268
|
+
)
|
|
269
|
+
if (isLive) {
|
|
270
|
+
Row(
|
|
271
|
+
Modifier
|
|
272
|
+
.padding(6.dp)
|
|
273
|
+
.clip(RoundedCornerShape(4.dp))
|
|
274
|
+
.background(YouTubeColors.Red)
|
|
275
|
+
.padding(horizontal = 6.dp, vertical = 4.dp)
|
|
276
|
+
.align(Alignment.TopStart),
|
|
277
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
278
|
+
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
279
|
+
) {
|
|
280
|
+
Box(Modifier.size(6.dp).clip(CircleShape).background(Color.White))
|
|
281
|
+
Text("LIVE", style = YouTubeText.DurationTag.copy(fontWeight = FontWeight.Bold), color = Color.White)
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
Text(
|
|
285
|
+
duration,
|
|
286
|
+
style = YouTubeText.DurationTag,
|
|
287
|
+
color = Color.White,
|
|
288
|
+
modifier = Modifier
|
|
289
|
+
.padding(6.dp)
|
|
290
|
+
.align(Alignment.BottomEnd)
|
|
291
|
+
.clip(RoundedCornerShape(4.dp))
|
|
292
|
+
.background(YouTubeColors.Scrim)
|
|
293
|
+
.padding(horizontal = 6.dp, vertical = 4.dp),
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
Row(
|
|
298
|
+
Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 12.dp, bottom = 16.dp),
|
|
299
|
+
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
300
|
+
) {
|
|
301
|
+
AsyncImage(
|
|
302
|
+
model = channelAvatarUrl,
|
|
303
|
+
contentDescription = null,
|
|
304
|
+
modifier = Modifier.size(28.dp).clip(CircleShape),
|
|
305
|
+
contentScale = ContentScale.Crop,
|
|
306
|
+
)
|
|
307
|
+
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
308
|
+
Text(title, style = YouTubeText.VideoTitle, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
|
309
|
+
Text(
|
|
310
|
+
"$channelName · $viewCount · $uploadedAgo",
|
|
311
|
+
style = YouTubeText.Metadata,
|
|
312
|
+
color = YouTubeColors.TextSecondaryLight,
|
|
313
|
+
maxLines = 1,
|
|
314
|
+
overflow = TextOverflow.Ellipsis,
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
Icon(Icons.Filled.MoreVert, contentDescription = "More", modifier = Modifier.size(20.dp))
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Action Pill (Like / Dislike / Share — 18dp rounded rect)
|
|
324
|
+
|
|
325
|
+
```kotlin
|
|
326
|
+
import androidx.compose.ui.graphics.vector.ImageVector
|
|
327
|
+
|
|
328
|
+
@Composable
|
|
329
|
+
fun ActionPill(
|
|
330
|
+
icon: ImageVector,
|
|
331
|
+
label: String?, // null for Dislike (count hidden per 2021 decision)
|
|
332
|
+
active: Boolean,
|
|
333
|
+
onClick: () -> Unit,
|
|
334
|
+
darkTheme: Boolean = false,
|
|
335
|
+
) {
|
|
336
|
+
val bg = if (darkTheme) YouTubeColors.Surface2Dark else YouTubeColors.Surface2Light
|
|
337
|
+
val fg = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
338
|
+
Row(
|
|
339
|
+
Modifier
|
|
340
|
+
.clip(RoundedCornerShape(18.dp))
|
|
341
|
+
.background(bg)
|
|
342
|
+
.clickable(onClick = onClick)
|
|
343
|
+
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
344
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
345
|
+
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
346
|
+
) {
|
|
347
|
+
Icon(icon, contentDescription = null, tint = fg, modifier = Modifier.size(20.dp))
|
|
348
|
+
if (label != null) Text(label, style = YouTubeText.LikeCount, color = fg)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Filter Chip Row (full color inversion when selected)
|
|
354
|
+
|
|
355
|
+
```kotlin
|
|
356
|
+
import androidx.compose.foundation.lazy.LazyRow
|
|
357
|
+
import androidx.compose.foundation.lazy.items
|
|
358
|
+
|
|
359
|
+
@Composable
|
|
360
|
+
fun FilterChipRow(
|
|
361
|
+
chips: List<String>,
|
|
362
|
+
selected: String,
|
|
363
|
+
onSelect: (String) -> Unit,
|
|
364
|
+
darkTheme: Boolean = false,
|
|
365
|
+
) {
|
|
366
|
+
val defaultBg = if (darkTheme) YouTubeColors.Surface2Dark else YouTubeColors.Surface2Light
|
|
367
|
+
val defaultFg = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
368
|
+
val activeBg = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
369
|
+
val activeFg = if (darkTheme) YouTubeColors.CanvasDark else YouTubeColors.CanvasLight
|
|
370
|
+
|
|
371
|
+
LazyRow(
|
|
372
|
+
contentPadding = PaddingValues(horizontal = 12.dp),
|
|
373
|
+
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
374
|
+
) {
|
|
375
|
+
items(chips) { chip ->
|
|
376
|
+
val isSel = chip == selected
|
|
377
|
+
Box(
|
|
378
|
+
Modifier
|
|
379
|
+
.clip(RoundedCornerShape(percent = 50)) // full pill — chips only
|
|
380
|
+
.background(if (isSel) activeBg else defaultBg)
|
|
381
|
+
.clickable { onSelect(chip) }
|
|
382
|
+
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
383
|
+
) {
|
|
384
|
+
Text(chip, style = YouTubeText.Chip, color = if (isSel) activeFg else defaultFg)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## 4. 16:9 Thumbnails · Mini-Player Drag-Down · Shorts Rail
|
|
392
|
+
|
|
393
|
+
YouTube's three most distinctive systems. The **16:9 thumbnail** (above) is the atomic unit. The **mini-player** is YouTube's signature navigation pattern — the full player collapses into a persistent 72dp bar that survives navigation; drag it down past a threshold to dismiss. The **Shorts rail** is a right-edge action stack over a full-bleed 9:16 video.
|
|
394
|
+
|
|
395
|
+
```kotlin
|
|
396
|
+
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
|
397
|
+
import androidx.compose.foundation.gestures.DraggableAnchors
|
|
398
|
+
import androidx.compose.foundation.gestures.Orientation
|
|
399
|
+
import androidx.compose.foundation.gestures.anchoredDraggable
|
|
400
|
+
import androidx.compose.material.icons.filled.Close
|
|
401
|
+
import androidx.compose.material.icons.filled.Pause
|
|
402
|
+
import androidx.compose.material.icons.filled.PlayArrow
|
|
403
|
+
import kotlin.math.roundToInt
|
|
404
|
+
import androidx.compose.ui.unit.IntOffset
|
|
405
|
+
|
|
406
|
+
enum class PlayerState { Full, Mini, Dismissed }
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Mini-player: a 72dp bar above the bottom bar that persists across navigation.
|
|
410
|
+
* Drag up → expand to full player (use SharedTransitionLayout on the thumbnail).
|
|
411
|
+
* Drag down past threshold → dismiss (video stops).
|
|
412
|
+
*/
|
|
413
|
+
@OptIn(ExperimentalFoundationApi::class)
|
|
414
|
+
@Composable
|
|
415
|
+
fun MiniPlayer(
|
|
416
|
+
thumbnailUrl: String,
|
|
417
|
+
title: String,
|
|
418
|
+
channelName: String,
|
|
419
|
+
isPlaying: Boolean,
|
|
420
|
+
onPlayPause: () -> Unit,
|
|
421
|
+
onExpand: () -> Unit,
|
|
422
|
+
onDismiss: () -> Unit,
|
|
423
|
+
dragState: AnchoredDraggableState<PlayerState>,
|
|
424
|
+
modifier: Modifier = Modifier,
|
|
425
|
+
darkTheme: Boolean = false,
|
|
426
|
+
) {
|
|
427
|
+
val bg = if (darkTheme) YouTubeColors.Surface1Dark else YouTubeColors.CanvasLight
|
|
428
|
+
val fg = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
429
|
+
|
|
430
|
+
Row(
|
|
431
|
+
modifier = modifier
|
|
432
|
+
.offset { IntOffset(0, dragState.requireOffset().roundToInt()) }
|
|
433
|
+
.anchoredDraggable(dragState, Orientation.Vertical)
|
|
434
|
+
.fillMaxWidth()
|
|
435
|
+
.height(72.dp)
|
|
436
|
+
.background(bg)
|
|
437
|
+
.clickable(onClick = onExpand),
|
|
438
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
439
|
+
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
440
|
+
) {
|
|
441
|
+
AsyncImage(
|
|
442
|
+
model = thumbnailUrl,
|
|
443
|
+
contentDescription = null,
|
|
444
|
+
contentScale = ContentScale.Crop,
|
|
445
|
+
modifier = Modifier.width(128.dp).height(72.dp), // 16:9, hard edges
|
|
446
|
+
)
|
|
447
|
+
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
|
448
|
+
Text(title, style = YouTubeText.ChannelName, color = fg, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
|
449
|
+
Text(channelName, style = YouTubeText.Timestamp, color = YouTubeColors.TextSecondaryLight, maxLines = 1)
|
|
450
|
+
}
|
|
451
|
+
Icon(
|
|
452
|
+
if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
|
453
|
+
contentDescription = if (isPlaying) "Pause" else "Play",
|
|
454
|
+
tint = fg,
|
|
455
|
+
modifier = Modifier.size(20.dp).clickable(onClick = onPlayPause),
|
|
456
|
+
)
|
|
457
|
+
Icon(
|
|
458
|
+
Icons.Filled.Close,
|
|
459
|
+
contentDescription = "Close",
|
|
460
|
+
tint = fg,
|
|
461
|
+
modifier = Modifier.padding(end = 8.dp).size(20.dp).clickable(onClick = onDismiss),
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Shorts Right-Rail (over full-bleed 9:16 video)
|
|
468
|
+
|
|
469
|
+
```kotlin
|
|
470
|
+
import androidx.compose.material.icons.filled.AddCircle
|
|
471
|
+
import androidx.compose.material.icons.filled.ThumbDown
|
|
472
|
+
import androidx.compose.material.icons.filled.ThumbUp
|
|
473
|
+
import androidx.compose.material.icons.outlined.Comment
|
|
474
|
+
import androidx.compose.material.icons.outlined.Reply
|
|
475
|
+
import androidx.compose.material.icons.outlined.ThumbDownOffAlt
|
|
476
|
+
import androidx.compose.material.icons.outlined.ThumbUpOffAlt
|
|
477
|
+
import androidx.compose.foundation.border
|
|
478
|
+
import androidx.compose.ui.draw.shadow
|
|
479
|
+
|
|
480
|
+
@Composable
|
|
481
|
+
fun ShortsActionRail(
|
|
482
|
+
creatorAvatarUrl: String,
|
|
483
|
+
likeCount: String,
|
|
484
|
+
commentCount: String,
|
|
485
|
+
liked: Boolean,
|
|
486
|
+
onLike: () -> Unit,
|
|
487
|
+
modifier: Modifier = Modifier,
|
|
488
|
+
) {
|
|
489
|
+
// White glyphs with a soft shadow for legibility over arbitrary video
|
|
490
|
+
@Composable
|
|
491
|
+
fun RailIcon(icon: ImageVector, label: String?, cd: String, onClick: () -> Unit) {
|
|
492
|
+
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
493
|
+
Icon(
|
|
494
|
+
icon, contentDescription = cd, tint = Color.White,
|
|
495
|
+
modifier = Modifier
|
|
496
|
+
.size(32.dp)
|
|
497
|
+
.shadow(2.dp, CircleShape, ambientColor = Color.Black.copy(alpha = 0.4f))
|
|
498
|
+
.clickable(onClick = onClick),
|
|
499
|
+
)
|
|
500
|
+
if (label != null) Text(label, style = YouTubeText.LikeCount, color = Color.White)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
Column(
|
|
505
|
+
modifier = modifier,
|
|
506
|
+
verticalArrangement = Arrangement.spacedBy(20.dp),
|
|
507
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
508
|
+
) {
|
|
509
|
+
Box(contentAlignment = Alignment.BottomEnd) {
|
|
510
|
+
AsyncImage(
|
|
511
|
+
model = creatorAvatarUrl,
|
|
512
|
+
contentDescription = "Channel",
|
|
513
|
+
contentScale = ContentScale.Crop,
|
|
514
|
+
modifier = Modifier.size(44.dp).clip(CircleShape).border(2.dp, Color.White, CircleShape),
|
|
515
|
+
)
|
|
516
|
+
Icon( // Subscribe shows as a red "+" badge on the avatar
|
|
517
|
+
Icons.Filled.AddCircle,
|
|
518
|
+
contentDescription = "Subscribe",
|
|
519
|
+
tint = YouTubeColors.Red,
|
|
520
|
+
modifier = Modifier.size(20.dp).offset(x = 4.dp, y = 4.dp),
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
RailIcon(if (liked) Icons.Filled.ThumbUp else Icons.Outlined.ThumbUpOffAlt, likeCount, "Like", onLike)
|
|
524
|
+
RailIcon(Icons.Outlined.ThumbDownOffAlt, null, "Dislike") {} // count hidden
|
|
525
|
+
RailIcon(Icons.Outlined.Comment, commentCount, "Comments") {}
|
|
526
|
+
RailIcon(Icons.Outlined.Reply, null, "Share") {}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
The Shorts progress scrubber at the bottom is **white, not red** (kept minimal): a 2dp track of `YouTubeColors.ShortsTrack` with a `Color.White` fill — never the brand red on Shorts.
|
|
532
|
+
|
|
533
|
+
## 5. Bottom Navigation (Tab Bar)
|
|
534
|
+
|
|
535
|
+
Use Material 3 `NavigationBar` with the five canonical tabs (Home, Shorts, Create, Subscriptions, You). YouTube uses a 1dp top hairline rather than a shadow; Android has no live blur, so use an opaque canvas surface with a top divider. **Active tint is achromatic** (`#0F0F0F`/`#FFFFFF`) — never red. The Create tab opens a modal sheet ("Create a Short", "Upload video", "Go live").
|
|
536
|
+
|
|
537
|
+
```kotlin
|
|
538
|
+
import androidx.compose.material.icons.filled.AddCircleOutline
|
|
539
|
+
import androidx.compose.material.icons.filled.Home
|
|
540
|
+
import androidx.compose.material.icons.filled.PlayCircleFilled
|
|
541
|
+
import androidx.compose.material.icons.filled.Subscriptions
|
|
542
|
+
import androidx.compose.material.icons.outlined.HomeOutlined
|
|
543
|
+
import androidx.compose.material.icons.outlined.PlayCircleOutline
|
|
544
|
+
import androidx.compose.material.icons.outlined.SubscriptionsOutlined
|
|
545
|
+
import androidx.compose.material.icons.outlined.AccountCircle
|
|
546
|
+
import androidx.compose.material3.HorizontalDivider
|
|
547
|
+
import androidx.compose.material3.NavigationBar
|
|
548
|
+
import androidx.compose.material3.NavigationBarItem
|
|
549
|
+
import androidx.compose.material3.NavigationBarItemDefaults
|
|
550
|
+
|
|
551
|
+
@Composable
|
|
552
|
+
fun YouTubeBottomBar(selected: Int, onSelect: (Int) -> Unit, darkTheme: Boolean = false) {
|
|
553
|
+
val container = if (darkTheme) YouTubeColors.CanvasDark else YouTubeColors.CanvasLight
|
|
554
|
+
val divider = if (darkTheme) YouTubeColors.DividerDark else YouTubeColors.DividerLight
|
|
555
|
+
val active = if (darkTheme) YouTubeColors.TextPrimaryDark else YouTubeColors.TextPrimaryLight
|
|
556
|
+
val inactive = if (darkTheme) YouTubeColors.TextSecondaryDark else YouTubeColors.TextSecondaryLight
|
|
557
|
+
|
|
558
|
+
Column {
|
|
559
|
+
HorizontalDivider(thickness = 1.dp, color = divider) // hairline, not a shadow
|
|
560
|
+
NavigationBar(containerColor = container, tonalElevation = 0.dp) {
|
|
561
|
+
data class Tab(val on: ImageVector, val off: ImageVector, val label: String)
|
|
562
|
+
val tabs = listOf(
|
|
563
|
+
Tab(Icons.Filled.Home, Icons.Outlined.HomeOutlined, "Home"),
|
|
564
|
+
Tab(Icons.Filled.PlayCircleFilled, Icons.Outlined.PlayCircleOutline, "Shorts"),
|
|
565
|
+
Tab(Icons.Filled.AddCircleOutline, Icons.Filled.AddCircleOutline, "Create"),
|
|
566
|
+
Tab(Icons.Filled.Subscriptions, Icons.Outlined.SubscriptionsOutlined, "Subscriptions"),
|
|
567
|
+
Tab(Icons.Outlined.AccountCircle, Icons.Outlined.AccountCircle, "You"),
|
|
568
|
+
)
|
|
569
|
+
tabs.forEachIndexed { i, tab ->
|
|
570
|
+
val isSel = selected == i
|
|
571
|
+
NavigationBarItem(
|
|
572
|
+
selected = isSel,
|
|
573
|
+
onClick = { onSelect(i) },
|
|
574
|
+
icon = { Icon(if (isSel) tab.on else tab.off, contentDescription = tab.label, modifier = Modifier.size(24.dp)) },
|
|
575
|
+
label = { Text(tab.label, style = YouTubeText.TabLabel) },
|
|
576
|
+
alwaysShowLabel = true,
|
|
577
|
+
colors = NavigationBarItemDefaults.colors(
|
|
578
|
+
selectedIconColor = active, // achromatic, NOT red
|
|
579
|
+
selectedTextColor = active,
|
|
580
|
+
unselectedIconColor = inactive,
|
|
581
|
+
unselectedTextColor = inactive,
|
|
582
|
+
indicatorColor = Color.Transparent, // no Material pill — YouTube has none
|
|
583
|
+
),
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
The **mini-player** renders in the `Scaffold` `bottomBar` slot stacked directly above `YouTubeBottomBar` while playback is active.
|
|
592
|
+
|
|
593
|
+
## 6. Motion
|
|
594
|
+
|
|
595
|
+
| Moment | Compose recipe |
|
|
596
|
+
|--------|----------------|
|
|
597
|
+
| Subscribe morph | `animateColorAsState` red ↔ gray with `spring(dampingRatio = 0.8f)` (~200ms); bell `AnimatedVisibility` slide+fade; `HapticFeedbackType.LongPress` |
|
|
598
|
+
| Mini-player ↔ full | `SharedTransitionLayout` + `Modifier.sharedElement()` on the 16:9 thumbnail, `spring(dampingRatio = 0.8f, stiffness = 250f)` |
|
|
599
|
+
| Mini-player dismiss | `AnchoredDraggable` past the dismiss anchor → `PlayerState.Dismissed` |
|
|
600
|
+
| Like tap | `Animatable` scale 1 → 1.15 → 1 over 300ms; `HapticFeedbackType.TextHandleMove` |
|
|
601
|
+
| Scrubber drag | thumb scale 1 → 1.3 while dragging; `HapticFeedbackType.SegmentTick` every 10% |
|
|
602
|
+
| Shorts swipe | `VerticalPager` 60fps, `spring(dampingRatio = 0.85f)` |
|
|
603
|
+
| Double-tap seek | tap-zone `pointerInput`; ±10s with an expanding arc-ripple overlay + "−10"/"+10" label |
|
|
604
|
+
| Filter chip select | `animateColorAsState` 200ms inversion; `HapticFeedbackType.SegmentTick` |
|
|
605
|
+
|
|
606
|
+
```kotlin
|
|
607
|
+
// Like bounce — thumbs-up scales 1 → 1.15 → 1 with a light haptic
|
|
608
|
+
import androidx.compose.animation.core.Animatable
|
|
609
|
+
import androidx.compose.animation.core.tween
|
|
610
|
+
import androidx.compose.ui.draw.scale
|
|
611
|
+
|
|
612
|
+
@Composable
|
|
613
|
+
fun LikeButton(initiallyLiked: Boolean = false) {
|
|
614
|
+
var liked by remember { mutableStateOf(initiallyLiked) }
|
|
615
|
+
val scale = remember { Animatable(1f) }
|
|
616
|
+
val haptics = LocalHapticFeedback.current
|
|
617
|
+
LaunchedEffect(liked) {
|
|
618
|
+
if (liked) {
|
|
619
|
+
haptics.performHapticFeedback(HapticFeedbackType.TextHandleMove) // ~iOS .impact(.light)
|
|
620
|
+
scale.animateTo(1.15f, spring(dampingRatio = 0.6f, stiffness = 600f))
|
|
621
|
+
scale.animateTo(1f, tween(150))
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
Icon(
|
|
625
|
+
imageVector = if (liked) Icons.Filled.ThumbUp else Icons.Outlined.ThumbUpOffAlt,
|
|
626
|
+
contentDescription = "Like",
|
|
627
|
+
modifier = Modifier.size(20.dp).scale(scale.value).clickable { liked = !liked },
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Haptics: prefer `LocalHapticFeedback`. `HapticFeedbackType.SegmentTick` ≈ iOS `.selection` for filter chips and scrubber ticks; `HapticFeedbackType.LongPress` ≈ `.impact(.light)`/`.success` for Subscribe. For richer control use `LocalView.current.performHapticFeedback(HapticFeedbackConstants.CONFIRM)` (API 30+) on the Subscribe success, or a `Vibrator` `VibrationEffect.createOneShot(10, 70)`.
|
|
633
|
+
|
|
634
|
+
## 7. Icons
|
|
635
|
+
|
|
636
|
+
YouTube ships custom glyphs (the logomark, the Shorts/Subscriptions marks); the closest first-party set is `androidx.compose.material:material-icons-extended`. The YouTube logomark (red play-button square) and the bespoke Shorts glyph should ship as vector drawables loaded via `ImageVector.vectorResource(R.drawable.…)` — the logomark's red is brand, never a UI accent.
|
|
637
|
+
|
|
638
|
+
| Purpose | SF Symbol (iOS) | Material Icon (Compose) |
|
|
639
|
+
|---------|-----------------|--------------------------|
|
|
640
|
+
| Play / Pause (player) | `play.fill` / `pause.fill` | `Icons.Filled.PlayArrow` / `Icons.Filled.Pause` |
|
|
641
|
+
| Next / Previous | `forward.end.fill` / `backward.end.fill` | `Icons.Filled.SkipNext` / `Icons.Filled.SkipPrevious` |
|
|
642
|
+
| Like | `hand.thumbsup` / `.fill` | `Icons.Outlined.ThumbUpOffAlt` / `Icons.Filled.ThumbUp` |
|
|
643
|
+
| Dislike | `hand.thumbsdown` | `Icons.Outlined.ThumbDownOffAlt` |
|
|
644
|
+
| Share | `arrowshape.turn.up.right` | `Icons.Outlined.Reply` (mirrored) / `Icons.Filled.Share` |
|
|
645
|
+
| Download | `arrow.down.circle` | `Icons.Outlined.FileDownload` |
|
|
646
|
+
| Save (playlist) | `plus.rectangle.on.folder` | `Icons.Outlined.PlaylistAdd` |
|
|
647
|
+
| Comment | `bubble.right` | `Icons.Outlined.Comment` |
|
|
648
|
+
| Subscribe bell | `bell` / `bell.fill` | `Icons.Outlined.NotificationsNone` / `Icons.Filled.Notifications` |
|
|
649
|
+
| Cast | `airplayvideo` | `Icons.Filled.Cast` |
|
|
650
|
+
| Search | `magnifyingglass` | `Icons.Filled.Search` |
|
|
651
|
+
| Home (tab) | `house` / `.fill` | `Icons.Outlined.HomeOutlined` / `Icons.Filled.Home` |
|
|
652
|
+
| Shorts (tab) | `play.rectangle` / `.fill` | `Icons.Outlined.PlayCircleOutline` (or vector drawable) |
|
|
653
|
+
| Create (tab) | `plus.circle.fill` | `Icons.Filled.AddCircleOutline` |
|
|
654
|
+
| Subscriptions (tab) | `play.square.stack` | `Icons.Filled.Subscriptions` |
|
|
655
|
+
| You (tab) | `person.crop.circle` | `Icons.Outlined.AccountCircle` |
|
|
656
|
+
| More | `ellipsis` | `Icons.Filled.MoreVert` |
|
|
657
|
+
| Close | `xmark` | `Icons.Filled.Close` |
|
|
658
|
+
|
|
659
|
+
## 8. Minimum SDK & Accessibility Notes
|
|
660
|
+
|
|
661
|
+
- **minSdk 24** (Compose floor is 21; `AnchoredDraggable`, `SharedTransitionLayout` (Compose 1.7+), and `VerticalPager` are comfortable at 24). `compileSdk 34+`, Compose BOM `2024.09+`, Kotlin `2.0+`.
|
|
662
|
+
- **Edge-to-edge**: call `enableEdgeToEdge()` in the `Activity`; system-bar icon color follows the theme. Let Shorts video extend under the system bars while keeping overlays inside `WindowInsets.systemBars`; apply `Scaffold` insets so the mini-player + tab bar clear the gesture nav. Video detail should support landscape fullscreen (let the player ignore insets, controls respect them).
|
|
663
|
+
- **Font scaling**: `sp` honors the user's font scale — keep it on video titles, descriptions, and comments. **Pin** the duration tag (11sp), Subscribe button text (14sp), tab labels (10sp), and Shorts captions by deriving them from a fixed-`fontScale` `LocalDensity` — these are layout-critical (the tag overlays the thumbnail corner; the chip/tab rows must not wrap).
|
|
664
|
+
- **TalkBack**: merge each video card into one node with `Modifier.semantics(mergeDescendants = true)` and a readable label (`"<title>, <channel>, <views>, <age>"`); expose Subscribe and the overflow as separate buttons. Shorts rail icons are independent buttons with state-aware descriptions.
|
|
665
|
+
- **Touch targets**: Material guidance is 48.dp minimum. The Subscribe pill is 36dp tall — expand the hit area to 48dp via padding. The 20dp overflow and 20dp mini-player controls need a 44–48dp hit area; Shorts 32dp glyphs need a 48dp hit area.
|
|
666
|
+
- **Reduce Motion**: respect the system "remove animations" setting — fall back the Subscribe morph to an instant cross-fade, skip the double-tap-seek ripple and the like bounce, and make the mini-player transition a snap.
|
|
667
|
+
- **Contrast**: `#606060` on `#FFFFFF` meets WCAG AA at 13sp+; `#AAAAAA` on `#0F0F0F` passes. Validate the lowest-emphasis tertiary (`#909090`/`#717171`) at the 11–12sp tag/timestamp sizes; Shorts captions over arbitrary video must keep the `rgba(0,0,0,0.5)` text shadow for legibility regardless of frame content.
|
|
668
|
+
- **Dynamic color**: YouTube is a Google product, but its brand red and the achromatic chrome are fixed — do **not** enable Material You `dynamicColorScheme()`; the Subscribe-red and near-black canvas must hold regardless of wallpaper. Keep the hand-built light/dark schemes above. Honor the device mute switch on Shorts autoplay and surface an audio-state indicator.
|