diff --git a/.changeset/bright-device-directory.md b/.changeset/bright-device-directory.md new file mode 100644 index 0000000..17d1e97 --- /dev/null +++ b/.changeset/bright-device-directory.md @@ -0,0 +1,5 @@ +--- +'@devicekit/frame': patch +--- + +Add a crawlable device-profile directory and static profile details to the demo. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 894502f..7eb1bde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - run: pnpm format:check - - run: pnpm build + - run: pnpm run check:device-pages - run: pnpm lint diff --git a/README.md b/README.md index 8ce5eeb..9eccab2 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Device profiles and a Web Component for rendering phone and tablet previews. [Open the live demo](https://echotechfe.github.io/devicekit/) +[Browse every device profile page](docs/device-pages.md) + ## Install Install the frame package to render previews: diff --git a/README.zh-CN.md b/README.zh-CN.md index 594e84f..c28eb29 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -12,6 +12,8 @@ [打开在线演示](https://echotechfe.github.io/devicekit/) +[查看全部机型页面](docs/device-pages.md) + ## 安装 需要渲染设备外观时安装 frame 包: diff --git a/docs/device-pages.md b/docs/device-pages.md new file mode 100644 index 0000000..e0d707c --- /dev/null +++ b/docs/device-pages.md @@ -0,0 +1,183 @@ +# Device demo pages + +This generated index lists every device profile with its public DeviceKit demo URL. It is kept in the repository so the complete set of pages is visible on GitHub as well as in the live site directory and sitemap. + +Do not edit this file by hand. Run `pnpm generate:device-pages` after changing the device table. + +| Device | OS | CSS viewport | Pixel ratio | Live demo | +| --- | --- | ---: | ---: | --- | +| iPhone SE | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-se/) | +| iPhone XR | ios | 414 × 896 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-xr/) | +| iPhone 12 Pro | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-pro/) | +| iPhone 14 | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-14/) | +| iPhone 14 Plus | ios | 428 × 926 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-14-plus/) | +| iPhone 14 Pro | ios | 393 × 852 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-14-pro/) | +| iPhone 14 Pro Max | ios | 430 × 932 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-14-pro-max/) | +| iPhone 15 | ios | 393 × 852 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-15/) | +| iPhone 15 Plus | ios | 430 × 932 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-15-plus/) | +| iPhone 15 Pro | ios | 393 × 852 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-15-pro/) | +| iPhone 15 Pro Max | ios | 430 × 932 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-15-pro-max/) | +| iPhone 16e | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-16e/) | +| iPhone 16 | ios | 393 × 852 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-16/) | +| iPhone 16 Plus | ios | 430 × 932 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-16-plus/) | +| iPhone 16 Pro | ios | 402 × 874 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-16-pro/) | +| iPhone 16 Pro Max | ios | 440 × 956 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-16-pro-max/) | +| iPad Mini | ios | 744 × 1133 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-mini/) | +| iPad Pro 13 | ios | 1032 × 1376 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro-13/) | +| iPhone 5 | ios | 320 × 568 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-5/) | +| iPhone 6/7/8 | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-6-7-8/) | +| iPhone 6/7/8 Plus | ios | 414 × 736 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-6-7-8-plus/) | +| iPhone X | ios | 375 × 812 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-x/) | +| iPhone XS Max | ios | 414 × 896 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-xs-max/) | +| iPhone 12/13 mini | ios | 375 × 812 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-13-mini/) | +| iPhone 12/13 (Pro) | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-13-pro/) | +| iPhone 12/13 Pro Max | ios | 428 × 926 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-13-pro-max/) | +| iPad | ios | 768 × 1024 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad/) | +| iPad Pro 10.5-inch | ios | 834 × 1112 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro-10-5-inch/) | +| iPad Pro 12.9-inch | ios | 1024 × 1366 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro-12-9-inch/) | +| iPad (gen 5) | ios | 768 × 1024 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-gen-5/) | +| iPad (gen 6) | ios | 768 × 1024 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-gen-6/) | +| iPad (gen 7) | ios | 810 × 1080 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-gen-7/) | +| iPad (gen 11) | ios | 820 × 1180 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-gen-11/) | +| iPad Pro 11 | ios | 834 × 1194 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro-11/) | +| iPhone 6 | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-6/) | +| iPhone 6 Plus | ios | 414 × 736 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-6-plus/) | +| iPhone 7 | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-7/) | +| iPhone 7 Plus | ios | 414 × 736 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-7-plus/) | +| iPhone 8 | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-8/) | +| iPhone 8 Plus | ios | 414 × 736 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-8-plus/) | +| iPhone SE (3rd gen) | ios | 375 × 667 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-se-3rd-gen/) | +| iPhone 11 | ios | 414 × 896 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-11/) | +| iPhone 11 Pro | ios | 375 × 812 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-11-pro/) | +| iPhone 11 Pro Max | ios | 414 × 896 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-11-pro-max/) | +| iPhone 12 | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12/) | +| iPhone 12 Pro Max | ios | 428 × 926 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-pro-max/) | +| iPhone 12 Mini | ios | 375 × 812 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-12-mini/) | +| iPhone 13 | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-13/) | +| iPhone 13 Pro | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-13-pro/) | +| iPhone 13 Pro Max | ios | 428 × 926 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-13-pro-max/) | +| iPhone 13 Mini | ios | 375 × 812 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-13-mini/) | +| iPhone 17 | ios | 402 × 874 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-17/) | +| iPhone Air | ios | 420 × 912 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-air/) | +| iPhone 17 Pro | ios | 402 × 874 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-17-pro/) | +| iPhone 17 Pro Max | ios | 440 × 956 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-17-pro-max/) | +| iPhone 18 Pro | ios | 402 × 874 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-18-pro/) | +| iPhone 18 Pro Max | ios | 440 × 956 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-18-pro-max/) | +| iPhone Duo (outer) | ios | 466 × 678 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-duo-outer/) | +| iPhone Duo (inner) | ios | 626 × 890 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-duo-inner/) | +| iPhone 17e | ios | 390 × 844 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-17e/) | +| iPhone 4 | ios | 320 × 480 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-4/) | +| iPhone 5/SE | ios | 320 × 568 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-5-se/) | +| iPad Air | ios | 820 × 1180 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-air/) | +| iPad Pro | ios | 1024 × 1366 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro/) | +| iPhone 17 Air | ios | 420 × 912 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/iphone-17-air/) | +| iPad Pro M4 | ios | 834 × 1210 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-pro-m4/) | +| iPad Air M2 | ios | 820 × 1180 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/ipad-air-m2/) | +| Pixel 7 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-7/) | +| Pixel 8 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-8/) | +| Pixel 8 Pro | android | 448 × 997 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-8-pro/) | +| Pixel 8a | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-8a/) | +| Pixel 9 | android | 412 × 924 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9/) | +| Pixel 9 Pro | android | 427 × 952 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9-pro/) | +| Pixel 9 Pro XL | android | 448 × 997 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9-pro-xl/) | +| Pixel 10 | android | 412 × 924 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-10/) | +| Samsung Galaxy S8+ | android | 360 × 740 | 4× | [Open demo](https://echotechfe.github.io/devicekit/devices/samsung-galaxy-s8/) | +| Samsung Galaxy S20 Ultra | android | 412 × 915 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/samsung-galaxy-s20-ultra/) | +| Surface Duo | android | 540 × 720 | 2.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/surface-duo/) | +| Surface Duo (inner) | android | 720 × 1114 | 2.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/surface-duo-inner/) | +| Samsung Galaxy A55 | android | 360 × 800 | 2.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/samsung-galaxy-a55/) | +| Pixel 9 Pro Fold | android | 412 × 922 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9-pro-fold/) | +| Pixel 9 Pro Fold (inner) | android | 836 × 842 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9-pro-fold-inner/) | +| Galaxy Z Fold 6 | android | 412 × 968 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-6/) | +| Galaxy Z Fold 6 (inner) | android | 707 × 823 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-6-inner/) | +| Galaxy Z Fold 5 | android | 344 × 882 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-5/) | +| Galaxy Z Fold 5 (inner) | android | 690 × 829 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-5-inner/) | +| Samsung Galaxy A51/71 | android | 412 × 914 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/samsung-galaxy-a51-71/) | +| Galaxy Tab S4 | android | 712 × 1138 | 2.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-tab-s4/) | +| Moto G4 | android | 360 × 640 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/moto-g4/) | +| Moto G Power | android | 412 × 823 | 1.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/moto-g-power/) | +| Nexus 5 | android | 360 × 640 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-5/) | +| Nexus 5X | android | 411 × 731 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-5x/) | +| Nexus 6 | android | 412 × 732 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-6/) | +| Galaxy Note 3 | android | 360 × 640 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-note-3/) | +| Galaxy Note II | android | 360 × 640 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-note-ii/) | +| Galaxy S III | android | 360 × 640 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s-iii/) | +| Galaxy S5 | android | 360 × 640 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s5/) | +| Galaxy S9+ | android | 320 × 658 | 4.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s9/) | +| Galaxy S24 | android | 360 × 780 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s24/) | +| Galaxy Tab S9 | android | 640 × 1024 | 2.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-tab-s9/) | +| Galaxy Z Fold 6 Cover | android | 484 × 1188 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-6-cover/) | +| Galaxy Z Fold 7 | android | 984 × 1092 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-7/) | +| Galaxy Z Fold 7 Cover | android | 360 × 840 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-7-cover/) | +| Galaxy Z Flip 6 | android | 360 × 880 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-flip-6/) | +| Galaxy Z Flip 6 Cover | android | 360 × 374 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-flip-6-cover/) | +| Galaxy Z Flip 7 | android | 360 × 840 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-flip-7/) | +| Galaxy Z Flip 7 Cover | android | 474 × 524 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-flip-7-cover/) | +| LG Optimus L70 | android | 384 × 640 | 1.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/lg-optimus-l70/) | +| Nexus 10 | android | 800 × 1280 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-10/) | +| Nexus 4 | android | 384 × 640 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-4/) | +| Nexus 6P | android | 412 × 732 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-6p/) | +| Nexus 7 | android | 600 × 960 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/nexus-7/) | +| Pixel 2 | android | 411 × 731 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-2/) | +| Pixel 2 XL | android | 411 × 823 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-2-xl/) | +| Pixel 3 | android | 393 × 786 | 2.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-3/) | +| Pixel 4 | android | 353 × 745 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-4/) | +| Pixel 4a (5G) | android | 412 × 892 | 2.63× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-4a-5g/) | +| Pixel 5 | android | 393 × 851 | 2.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-5/) | +| Pixel 6 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-6/) | +| Pixel 6 Pro | android | 412 × 892 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-6-pro/) | +| Pixel 6a | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-6a/) | +| Pixel 7 Pro | android | 412 × 892 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-7-pro/) | +| Pixel 7a | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-7a/) | +| Pixel 10 Pro | android | 427 × 952 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-10-pro/) | +| Pixel 10 Pro XL | android | 448 × 997 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-10-pro-xl/) | +| Pixel 3 XL | android | 393 × 786 | 2.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-3-xl/) | +| Pixel Fold | android | 734 × 1014 | 2.8× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-fold/) | +| Pixel Tablet | android | 800 × 1280 | 2× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-tablet/) | +| Pixel 9a | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/pixel-9a/) | +| Galaxy Fold | android | 280 × 653 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-fold/) | +| Galaxy S21 | android | 360 × 800 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s21/) | +| Galaxy S21 Plus | android | 384 × 854 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s21-plus/) | +| Galaxy S21 Ultra | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s21-ultra/) | +| Galaxy S20 | android | 360 × 800 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s20/) | +| Galaxy S20 Plus | android | 384 × 854 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s20-plus/) | +| Samsung S21 FE | android | 360 × 800 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/samsung-s21-fe/) | +| Galaxy Fold3 (Folded) | android | 320 × 872 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-fold3-folded/) | +| Galaxy Fold3 (Unfolded) | android | 590 × 736 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-fold3-unfolded/) | +| Galaxy S22 | android | 360 × 800 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22/) | +| Galaxy S22 Plus | android | 384 × 854 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22-plus/) | +| Galaxy S22 Ultra | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22-ultra/) | +| Galaxy S23 | android | 360 × 800 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23/) | +| Galaxy S23 Plus | android | 384 × 854 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23-plus/) | +| Galaxy S23 Ultra | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23-ultra/) | +| Galaxy S24 Plus | android | 384 × 854 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s24-plus/) | +| Galaxy S24 Ultra | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s24-ultra/) | +| Galaxy Z Flip 5 | android | 264 × 844 | 2.8× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-flip-5/) | +| Galaxy Z Fold 4 | android | 344 × 882 | 2.8× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-z-fold-4/) | +| OnePlus 12 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/oneplus-12/) | +| Nothing Phone 2 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/nothing-phone-2/) | +| Xiaomi 14 | android | 412 × 915 | 2.625× | [Open demo](https://echotechfe.github.io/devicekit/devices/xiaomi-14/) | +| Motorola Razr+ | android | 264 × 844 | 2.8× | [Open demo](https://echotechfe.github.io/devicekit/devices/motorola-razr/) | +| OnePlus Open | android | 734 × 1014 | 2.8× | [Open demo](https://echotechfe.github.io/devicekit/devices/oneplus-open/) | +| HUAWEI Mate 80 | harmony | 366 × 809 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-80/) | +| HUAWEI Mate 70 | harmony | 374 × 827 | 3.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-70/) | +| HUAWEI Mate 70 Pro | harmony | 376 × 809 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-70-pro/) | +| HUAWEI Mate 60 | harmony | 374 × 827 | 3.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-60/) | +| HUAWEI Mate 60 Pro | harmony | 388 × 837 | 3.25× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-60-pro/) | +| HUAWEI Pura 80 Pro | harmony | 365 × 814 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-80-pro/) | +| HUAWEI nova 14 Ultra | harmony | 363 × 817 | 3.5× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-nova-14-ultra/) | +| HUAWEI Pura X (inner) | harmony | 440 × 707 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-x-inner/) | +| HUAWEI Pura X (outer) | harmony | 327 × 327 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-x-outer/) | +| HUAWEI Mate X6 (inner) | harmony | 717 × 781 | 3.125× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-x6-inner/) | +| HUAWEI Mate X6 (outer) | harmony | 346 × 781 | 3.125× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-x6-outer/) | +| HUAWEI Pura 70 | harmony | 372 × 818 | 3.375× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-70/) | +| HUAWEI Pura 70 Pro | harmony | 373 × 843 | 3.375× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-70-pro/) | +| HUAWEI Mate X5 (inner) | harmony | 712 × 799 | 3.125× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-x5-inner/) | +| HUAWEI Mate X5 (outer) | harmony | 346 × 801 | 3.125× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-mate-x5-outer/) | +| HUAWEI Pocket 2 | harmony | 364 × 861 | 3.125× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pocket-2/) | +| HUAWEI nova 13 | harmony | 361 × 804 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-nova-13/) | +| HUAWEI nova 13 Pro | harmony | 363 × 823 | 3.375× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-nova-13-pro/) | +| HUAWEI nova 12 | harmony | 361 × 804 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-nova-12/) | +| HUAWEI nova 12 Pro | harmony | 363 × 823 | 3.375× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-nova-12-pro/) | +| HUAWEI Pura X Max (outer) | harmony | 460 × 672 | 2.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-x-max-outer/) | +| HUAWEI Pura X Max (inner) | harmony | 665 × 940 | 2.75× | [Open demo](https://echotechfe.github.io/devicekit/devices/huawei-pura-x-max-inner/) | diff --git a/package.json b/package.json index 95906a6..c24ae95 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "format:check": "oxfmt --check packages/devices/src/presets/ios.ts packages/devices/src/presets/android.ts packages/devices/src/presets/harmony.ts", "test": "pnpm -r test && pnpm run test:scripts", "test:scripts": "node --test scripts/*.test.mjs", + "generate:device-pages": "pnpm run build && node scripts/generate-device-pages.mjs", + "check:device-pages": "pnpm run build && node scripts/generate-device-pages.mjs --check", "test:coverage": "pnpm -r test:coverage", "lint": "oxlint .", "verify:pack": "pnpm -r build && node scripts/verify-pack.mjs", diff --git a/packages/frame/demo/src/components/DemoPage.astro b/packages/frame/demo/src/components/DemoPage.astro index f7c1f5a..224cf00 100644 --- a/packages/frame/demo/src/components/DemoPage.astro +++ b/packages/frame/demo/src/components/DemoPage.astro @@ -1,5 +1,6 @@ --- import type { PresetDeviceProfile } from '@devicekit/devices' +import { DEMO_DEVICE_DIRECTORY_URL } from '../../../src/demo-device-seo.js' import { demoPageMetadata } from '../../../src/demo-page-metadata.js' import '../../style.css' @@ -7,6 +8,7 @@ interface Props { device?: PresetDeviceProfile } const { device } = Astro.props const metadata = demoPageMetadata(device) +const deviceDirectoryHref = new URL(DEMO_DEVICE_DIRECTORY_URL).pathname --- @@ -25,6 +27,16 @@ const metadata = demoPageMetadata(device)
devicekit / frame

{metadata.heading}

GitHub ↗

{metadata.intro}

+ Browse all device profiles + {device !== undefined &&
+

Device profile specifications

+
+
Operating system
{device.os}
+
CSS viewport
{device.screen.width} × {device.screen.height}
+
Pixel ratio
{device.pixelRatio}×
+
Release year
{device.releaseYear}
+
+
}
Device
diff --git a/packages/frame/demo/src/pages/devices/index.astro b/packages/frame/demo/src/pages/devices/index.astro new file mode 100644 index 0000000..f1c8107 --- /dev/null +++ b/packages/frame/demo/src/pages/devices/index.astro @@ -0,0 +1,42 @@ +--- +import { DEVICES } from '@devicekit/devices' +import { DEMO_DEVICE_DIRECTORY_URL, DEMO_SITE_URL } from '../../../../src/demo-device-seo.js' +import { devicePath } from '../../../../src/demo-device-url.js' +import '../../../style.css' + +const deviceHref = (name: string) => new URL(devicePath(name), DEMO_SITE_URL).pathname +--- + + + + + + + All device profiles | devicekit + + + + + + + + + +
+

devicekit / frame

+

All device profiles

+

Choose one of {DEVICES.length} iOS, Android and HarmonyOS device profiles to inspect its viewport, pixel ratio, safe areas and device frame.

+

Open the device frame simulator

+ +
+ + diff --git a/packages/frame/demo/src/pages/sitemap.xml.ts b/packages/frame/demo/src/pages/sitemap.xml.ts index 96cb0ff..4b79fef 100644 --- a/packages/frame/demo/src/pages/sitemap.xml.ts +++ b/packages/frame/demo/src/pages/sitemap.xml.ts @@ -1,10 +1,10 @@ import { DEVICES } from '@devicekit/devices' -import { DEMO_SITE_URL, deviceCanonicalUrl } from '../../../src/demo-device-seo.js' +import { DEMO_DEVICE_DIRECTORY_URL, DEMO_SITE_URL, deviceCanonicalUrl } from '../../../src/demo-device-seo.js' export const prerender = true export function GET(): Response { - const urls = [DEMO_SITE_URL, ...DEVICES.map(deviceCanonicalUrl)] + const urls = [DEMO_SITE_URL, DEMO_DEVICE_DIRECTORY_URL, ...DEVICES.map(deviceCanonicalUrl)] const body = `\n\n${urls.map((url) => ` ${url}`).join('\n')}\n\n` return new Response(body, { headers: { 'Content-Type': 'application/xml' } }) } diff --git a/packages/frame/demo/style.css b/packages/frame/demo/style.css index 8a1d274..257968b 100644 --- a/packages/frame/demo/style.css +++ b/packages/frame/demo/style.css @@ -117,6 +117,61 @@ body { line-height: 1.45; } +.device-directory-link { + display: block; + margin: -8px 0 16px; + color: var(--demo-body-color); + font-size: 12px; + font-weight: 600; +} + +.device-profile { + margin: 16px 0; + padding: 14px; + border: 1px solid var(--demo-panel-border); + border-radius: 10px; + background: color-mix(in srgb, var(--demo-panel-background) 92%, #f4f4f5); +} + +.device-profile h2 { + margin: 0 0 10px; + font-size: 13px; +} + +.device-profile dl { margin: 0; } +.device-profile dl > div { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; } +.device-profile dt { color: var(--demo-label-color); } +.device-profile dd { margin: 0; font-weight: 600; text-align: right; } + +.device-directory-page { + display: block; + height: auto; + min-height: 100vh; +} + +.device-directory { + width: min(100% - 32px, 960px); + margin: 0 auto; + padding: 64px 0; +} + +.device-directory__eyebrow { + margin: 0 0 10px; + color: var(--demo-label-color); + font-size: 11px; + font-weight: 700; + letter-spacing: .09em; + text-transform: uppercase; +} + +.device-directory h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); letter-spacing: -.035em; } +.device-directory__intro { max-width: 720px; margin: 16px 0; color: var(--demo-note-color); font-size: 16px; } +.device-directory__home { color: var(--demo-body-color); font-weight: 600; } +.device-directory__list { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; margin: 32px 0 0; padding: 0; list-style: none; } +.device-directory__list li { padding: 14px; border: 1px solid var(--demo-panel-border); border-radius: 10px; background: var(--demo-panel-background); } +.device-directory__list a { display: block; color: var(--demo-body-color); font-weight: 700; } +.device-directory__list span { display: block; margin-top: 5px; color: var(--demo-note-color); font-size: 12px; } + .panel__eyebrow, .control-section__title, .stage__label { diff --git a/packages/frame/src/demo-crawlability.test.ts b/packages/frame/src/demo-crawlability.test.ts new file mode 100644 index 0000000..cde4465 --- /dev/null +++ b/packages/frame/src/demo-crawlability.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest' +// @ts-expect-error -- node:fs is available in the Vitest runtime only. +import { readFileSync } from 'node:fs' + +const deviceDirectory = readFileSync('demo/src/pages/devices/index.astro', 'utf8') +const demoPage = readFileSync('demo/src/components/DemoPage.astro', 'utf8') + +describe('demo crawlability', () => { + it('provides a static directory that links to every device detail page', () => { + expect(deviceDirectory).toContain('DEVICES.map') + expect(deviceDirectory).toContain('') + expect(deviceDirectory).toContain('new URL(devicePath(name), DEMO_SITE_URL).pathname') + }) + + it('renders the selected device facts and the directory link before JavaScript runs', () => { + expect(demoPage).toContain('id="device-directory"') + expect(demoPage).toContain('new URL(DEMO_DEVICE_DIRECTORY_URL).pathname') + expect(demoPage).toContain('Operating system') + expect(demoPage).toContain('CSS viewport') + expect(demoPage).toContain('Pixel ratio') + expect(demoPage).toContain('Release year') + }) +}) diff --git a/packages/frame/src/demo-device-seo.ts b/packages/frame/src/demo-device-seo.ts index 275e752..f81312b 100644 --- a/packages/frame/src/demo-device-seo.ts +++ b/packages/frame/src/demo-device-seo.ts @@ -2,6 +2,7 @@ import type { PresetDeviceProfile } from '@devicekit/devices' import { devicePath } from './demo-device-url.js' export const DEMO_SITE_URL = 'https://echotechfe.github.io/devicekit/' +export const DEMO_DEVICE_DIRECTORY_URL = new URL('devices/', DEMO_SITE_URL).href const OS_LABEL = { ios: 'iOS', android: 'Android', harmony: 'HarmonyOS' } as const @@ -28,7 +29,7 @@ export function devicePageJsonLd(device: PresetDeviceProfile): Record { expect(metadata.title).toBe('iPhone 18 Pro device frame simulator | devicekit') expect(metadata.heading).toBe('iPhone 18 Pro device frame simulator') expect(metadata.canonical).toBe('https://echotechfe.github.io/devicekit/devices/iphone-18-pro/') - expect(metadata.jsonLd).toMatchObject({ '@type': 'WebPage', url: metadata.canonical }) + expect(metadata.jsonLd).toMatchObject({ + '@type': 'WebPage', + url: metadata.canonical, + isPartOf: { url: 'https://echotechfe.github.io/devicekit/devices/' }, + }) }) it('makes browser history restore homepage metadata after a device route', () => { diff --git a/scripts/device-pages.test.mjs b/scripts/device-pages.test.mjs new file mode 100644 index 0000000..b599f5a --- /dev/null +++ b/scripts/device-pages.test.mjs @@ -0,0 +1,30 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const scriptsDir = path.dirname(fileURLToPath(import.meta.url)) +const repoRoot = path.resolve(scriptsDir, '..') +const pagesPath = path.join(repoRoot, 'docs/device-pages.md') +const deviceNamesPath = path.join(repoRoot, 'packages/devices/src/device-names.generated.ts') + +function deviceSlug(name) { + return name + .normalize('NFKD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') +} + +test('docs/device-pages.md lists exactly one live URL for every device profile', () => { + const markdown = readFileSync(pagesPath, 'utf8') + const names = [...readFileSync(deviceNamesPath, 'utf8').matchAll(/^ \w+: '([^']+)',$/gm)].map((match) => match[1]) + const urls = [...markdown.matchAll(/\((https:\/\/echotechfe\.github\.io\/devicekit\/devices\/[^)]+\/)\)/g)].map((match) => match[1]) + const expectedUrls = names.map((name) => `https://echotechfe.github.io/devicekit/devices/${deviceSlug(name)}/`) + + assert.ok(markdown.includes('pnpm generate:device-pages')) + assert.equal(urls.length, names.length) + assert.deepEqual(new Set(urls), new Set(expectedUrls)) +}) diff --git a/scripts/generate-device-pages.mjs b/scripts/generate-device-pages.mjs new file mode 100644 index 0000000..7b935a8 --- /dev/null +++ b/scripts/generate-device-pages.mjs @@ -0,0 +1,38 @@ +#!/usr/bin/env node +import { readFileSync, writeFileSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +const scriptsDir = path.dirname(fileURLToPath(import.meta.url)) +const repoRoot = path.resolve(scriptsDir, '..') +const outputFile = path.join(repoRoot, 'docs/device-pages.md') +const checkOnly = process.argv.includes('--check') +const devicesModule = pathToFileURL(path.join(repoRoot, 'packages/devices/dist/presets/index.js')).href +const seoModule = pathToFileURL(path.join(repoRoot, 'packages/frame/dist/demo-device-seo.js')).href +const { DEVICES } = await import(devicesModule) +const { deviceCanonicalUrl } = await import(seoModule) + +const rows = DEVICES.map((device) => `| ${device.name} | ${device.os} | ${device.screen.width} × ${device.screen.height} | ${device.pixelRatio}× | [Open demo](${deviceCanonicalUrl(device)}) |`).join('\n') +const content = `# Device demo pages + +This generated index lists every device profile with its public DeviceKit demo URL. It is kept in the repository so the complete set of pages is visible on GitHub as well as in the live site directory and sitemap. + +Do not edit this file by hand. Run \`pnpm generate:device-pages\` after changing the device table. + +| Device | OS | CSS viewport | Pixel ratio | Live demo | +| --- | --- | ---: | ---: | --- | +${rows} +` + +if (checkOnly) { + const current = readFileSync(outputFile, 'utf8') + if (current !== content) { + console.error('generate-device-pages: docs/device-pages.md is out of date.') + console.error('Run `pnpm generate:device-pages` and commit the result.') + process.exit(1) + } + console.log(`generate-device-pages: ${DEVICES.length} device page(s) are up to date.`) +} else { + writeFileSync(outputFile, content) + console.log(`generate-device-pages: wrote ${DEVICES.length} device page(s) to docs/device-pages.md`) +}