Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions docs/device-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Do not edit this file by hand. Run `pnpm generate:device-pages` after changing t
| 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 Duo (inner) | ios | 669 × 951 | 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/) |
Expand All @@ -82,10 +82,10 @@ Do not edit this file by hand. Run `pnpm generate:device-pages` after changing t
| 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/) |
| Samsung Galaxy S20 Ultra | android | 412 × 915 | 2.625× | [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/) |
| Samsung Galaxy A55 | android | 384 × 832 | 2.8125× | [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/) |
Expand All @@ -112,7 +112,7 @@ Do not edit this file by hand. Run `pnpm generate:device-pages` after changing t
| 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/) |
| Galaxy Z Flip 7 Cover | android | 361 × 399 | 2.625× | [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/) |
Expand All @@ -138,20 +138,20 @@ Do not edit this file by hand. Run `pnpm generate:device-pages` after changing t
| 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 S21 Ultra | android | 384 × 853 | 2.8125× | [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 S20 Plus | android | 384 × 854 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s20-plus/) |
| Samsung S21 FE | android | 360 × 780 | 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 S22 | android | 360 × 780 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22/) |
| Galaxy S22 Plus | android | 384 × 832 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22-plus/) |
| Galaxy S22 Ultra | android | 384 × 823 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s22-ultra/) |
| Galaxy S23 | android | 360 × 780 | 3× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23/) |
| Galaxy S23 Plus | android | 384 × 832 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23-plus/) |
| Galaxy S23 Ultra | android | 384 × 823 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s23-ultra/) |
| Galaxy S24 Plus | android | 384 × 832 | 2.8125× | [Open demo](https://echotechfe.github.io/devicekit/devices/galaxy-s24-plus/) |
| Galaxy S24 Ultra | android | 384 × 832 | 2.8125× | [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/) |
Expand Down
15 changes: 15 additions & 0 deletions packages/devices/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# @devicekit/devices

## Unreleased

### Minor Changes

- Screen-fidelity P0 follow-up (adversarial review):
- iOS ScreenCorners fractions: store **47.33** / **53.33** where measured (was truncated 47 / 53).
- iPhone Duo outer hinge corners **8** (DeviceHub / safearea.info); free edge remains 59 continuous.
- iPhone Duo **inner landscape** safe area **right 84 / bottom 34**.
- Face ID notch: per-family `bottomRadius` / cited `topRadius` (X/XS/11 Pro ear 6); uncalibrated ears stay unset.
- Classic drops **Pixel 8** until RoundedCorner is measured (no invented radius). Xiaomi 14 already omitted.
- Classic / default **Galaxy S24 Ultra** documents FHD+ window **384×832 @2.8125** (was silent 412×915); radius 2.13 noted as near-sharp under FHD+.
- Pixel 9 Pro status / safe top **52** + bottom **24** (emulator-tier windowinsets) called out vs older 68 strip.
- Android cutout audit: Pixel 5 / 9 Pro Fold centerX; Pixel 9/10 Pro Ø; S20+/Ultra holes; Pixel 3 XL notch; cite-only elsewhere.
- CI: Classic modern phones lock expected radius buckets; Harmony skip stays honest; cutout ⇒ explicit radius guard for modern iPhones.

## 0.4.1

No changes in this release.
Expand Down
37 changes: 34 additions & 3 deletions packages/devices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const windowSize = resolveWindowSize(profile, {

`DEVICES` contains the complete table. `IOS_DEVICES`, `ANDROID_DEVICES`, and `HARMONY_DEVICES` expose the platform lists. Folding devices use separate profiles for their inner and outer screens.

`CLASSIC_DEVICES` is a 19-profile subset for compact device selectors. `DEFAULT_DEVICE` is the iPhone X profile. `DEVICE_NAMES` provides typed names such as `DEVICE_NAMES.iPhone_16_Pro`.
`CLASSIC_DEVICES` is a 17-profile subset for compact device selectors (Pixel 8 and Xiaomi 14 are kept in `DEVICES` but omitted from Classic until screen radius / cutout / safe are measured). `DEFAULT_DEVICE` is the iPhone X profile. `DEVICE_NAMES` provides typed names such as `DEVICE_NAMES.iPhone_16_Pro`.

## Device profiles

Expand Down Expand Up @@ -100,10 +100,40 @@ const windowSize = resolveWindowSize(profile, {

`resolveSafeAreaInsets()` returns distances from the four screen edges. `resolveSafeArea()` returns `{ top, left, right, bottom, width, height }` in screen coordinates. `resolveWindowSize()` subtracts the status bar, the requested navigation bar, and `tabBarHeight` from the oriented screen.

The iPhone Duo rows are provisional: camera and status chrome are pixel-calibrated against Apple's website renders, while points, DPR, and safe-area values still await Xcode 27.1 DeviceHub verification.
iPhone Duo layout points, safe-area insets, and free-edge screen radius (59, continuous) follow Xcode 27.1 DeviceHub / safearea.info. Outer idle cutout remains Ø37 circle; hinge-side corners are **8**. Crease / pose APIs are not modeled yet.

`navigationBar` accepts `true`, `false`, or a non-negative number. `tabBarHeight` accepts a non-negative number. Invalid numeric options throw a `RangeError`.


## Galaxy S24 Ultra window mode (FHD+ default)

Classic / default `Galaxy S24 Ultra` uses the **FHD+ gesture window** reported by windowinsets.info at densityDpi 450:

| Field | Value |
| --- | --- |
| Logical window | **384×832** @ **2.8125** |
| Screen radius | **2.13** (WindowInsets.RoundedCorner — near-sharp content clip under FHD+; screenshot QA should expect a near-square content corner, not S24's 36) |
| Safe area | top ≈34 / bottom ≈15 |

This is **not** the panel's full WQHD+ logical size (previously shipped as 412×915 @2.625). Hosts that need the old WQHD+-ish window should pass an explicit `deviceProfile` override:

```ts
const ultra = findDevice('Galaxy S24 Ultra')!
const wqhd = {
...ultra,
screen: { width: 412, height: 915 },
pixelRatio: 2.625,
}
```

A named dual-field / `screenMode` API is tracked as P1. Until then, Classic consumers should treat 384×832 as the breaking default.

## Classic screen-fidelity notes

- Modern Classic phones (non-tablet, non-Harmony) lock an explicit measured `shell.screenRadius` in CI.
- Harmony Classic rows stay in the picker but **skip** the radius lock until RoundedCorner + bottom inset are measured — no invented cutouts.
- Pixel 8 and Xiaomi 14 remain in `ANDROID_DEVICES` / `DEVICES` but are **not** Classic.

## User agents

```ts
Expand All @@ -127,7 +157,7 @@ const version = systemVersion(profile)
| `IOS_DEVICES` | `readonly PresetDeviceProfile[]` | iOS profiles |
| `ANDROID_DEVICES` | `readonly PresetDeviceProfile[]` | Android profiles |
| `HARMONY_DEVICES` | `readonly PresetDeviceProfile[]` | HarmonyOS profiles |
| `CLASSIC_DEVICES` | `readonly PresetDeviceProfile[]` | 19 profiles for short selectors |
| `CLASSIC_DEVICES` | `readonly PresetDeviceProfile[]` | 17 profiles for short selectors |
| `DEFAULT_DEVICE` | `PresetDeviceProfile` | iPhone X profile |
| `PLATFORM_DEFAULTS` | `Record<DeviceOS, ...>` | Default bar and shell values by platform |
| `DEVICE_NAMES` | `const object` | Typed name constants generated from `DEVICES` |
Expand Down Expand Up @@ -170,6 +200,7 @@ const version = systemVersion(profile)
| `DeviceShell` | Screen radius and uniform `bezel`; `bezelInsets` can override individual edges, `homeButton` describes physical hardware, and per-edge profiles should set `bodyRadius` explicitly |
| `CornerRadii` | `{ topLeft, topRight, bottomRight, bottomLeft }`, ordered the way CSS `border-radius` orders them |
| `screenCorners` (on `DeviceShell`) | Per-corner override of `screenRadius`, for shells whose screen corners are not all the same — a folding phone's hinge side, say. Omitted corners keep the uniform `screenRadius` value |
| `cornerCurve` (on `DeviceShell`) | `'circular'` or `'continuous'`; applied only to `.screen` (iOS defaults to continuous / squircle; Android and HarmonyOS stay circular) |
| `HomeButtonSpec` | `{ diameter }`, the physical Home button centered in the body bezel |
| `ResolvedDeviceShell` | A shell with every edge resolved and `homeButton` set to a spec or `null` |
| `WindowSizeOptions` | Options for `resolveWindowSize()` |
Expand Down
31 changes: 28 additions & 3 deletions packages/devices/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,31 @@ const device = findDevice(DEVICE_NAMES.iPhone_16_Pro) ?? DEFAULT_DEVICE

`DEVICE_NAMES` 把表里每个 `name` 都变成一个能自动补全的常量,不用手打字符串:`DEVICE_NAMES.iPhone_16_Pro` 就是字符串 `'iPhone 16 Pro'`。键的推导规则是把机型名里每一段不属于 `[A-Za-z0-9]` 的字符合并成一个 `_`,再去掉首尾的 `_`——`'iPhone 12/13 (Pro)'` 变成 `iPhone_12_13_Pro`,`'iPad Pro 10.5-inch'` 变成 `iPad_Pro_10_5_inch`。写 `findDevice(DEVICE_NAMES.iPhone_16_Pro)` 传的还是字符串本身,常量的作用只是让手误变成一个不存在的属性报错,而不是查找悄悄落空。`DEVICE_NAMES` 来自一份生成文件,见下面的 API 一览。

`CLASSIC_DEVICES` 是从同一批对象里手选出的精简子集——19 台,按 iOS → Android → HarmonyOS 分组——给放不下 171 行的机型下拉框用。选哪几台是主观判断,不是排名:iPhone 和 iPad、Pixel 和 Galaxy,加一台小米和四台华为。需要完整机型表的宿主仍然读 `DEVICES`。
`CLASSIC_DEVICES` 是从同一批对象里手选出的精简子集——**17** 台,按 iOS → Android → HarmonyOS 分组——给放不下 171 行的机型下拉框用。选哪几台是主观判断,不是排名:iPhone 和 iPad、Pixel 9 Pro 与 Galaxy S24/Ultra,加四台华为。Pixel 8 与小米 14 仍在 `DEVICES` 里,但未测到 RoundedCorner / cutout / safe 前不进 Classic。需要完整机型表的宿主仍然读 `DEVICES`。


## Galaxy S24 Ultra 窗口模式(默认 FHD+)

Classic / 默认的 `Galaxy S24 Ultra` 使用 windowinsets.info 在 densityDpi 450 下报告的 **FHD+ 手势窗**:

| 字段 | 值 |
| --- | --- |
| 逻辑窗 | **384×832** @ **2.8125** |
| 屏幕圆角 | **2.13**(FHD+ 下接近直角裁切;截图验收不要按 S24 的 36 来比) |
| 安全区 | top ≈34 / bottom ≈15 |

这不是面板完整 WQHD+ 逻辑尺寸(旧值曾是 412×915 @2.625)。需要旧窗的宿主请显式覆盖 `deviceProfile`:

```ts
const ultra = findDevice('Galaxy S24 Ultra')!
const wqhd = {
...ultra,
screen: { width: 412, height: 915 },
pixelRatio: 2.625,
}
```

命名的双字段 / `screenMode` API 记为 P1。在此之前,Classic 消费者应把 384×832 当作破坏性默认。

## 一条机型长什么样

Expand Down Expand Up @@ -95,7 +119,7 @@ const resolved = resolveDevice(device)

明确**未核实**、因而留在默认值上没有猜的:

- iPhone Duo 的摄像头孔和专用状态栏已按 Apple 官网营销图做像素校准;points、DPR 和安全区仍未经过 Xcode 27.1 DeviceHub 实测。
- iPhone Duo 的布局点、安全区和自由边圆角(59,continuous)按 Xcode 27.1 DeviceHub / safearea.info;外屏空闲挖孔仍为 Ø37 圆;铰链边圆角(~6)仍为 provisional。折痕 / pose API 尚未建模。

- 鸿蒙的手势条高度——来源里没有这一项,所以这些机型报的底部安全区是 0,手势条也不画。
- 安卓和鸿蒙横屏的一切——包括状态栏转屏后是不是还在。机型不写横屏字段时并不会沿用自己竖屏的值,而是落到 `PLATFORM_DEFAULTS`:安卓固定 24、鸿蒙固定 36,跟这台机器竖屏实测多少无关(比如 Pixel 7 竖屏状态栏是 52,横屏没写就落到 24)。机型不写 `safeAreaInsetsLandscape` 时,顶部安全区仍然按横屏状态栏高度补齐,不会补成 0——安卓和鸿蒙转成横屏状态栏照样在,补 0 跟竖屏不测就漏填一样是错的。只有横屏状态栏本来就是 0 的 iOS 才能放心用空安全区兜底。
Expand Down Expand Up @@ -156,7 +180,7 @@ import { deviceUserAgent, systemVersion } from '@devicekit/devices'
| `IOS_DEVICES` | `readonly PresetDeviceProfile[]` | iOS 那部分 |
| `ANDROID_DEVICES` | `readonly PresetDeviceProfile[]` | Android 那部分 |
| `HARMONY_DEVICES` | `readonly PresetDeviceProfile[]` | HarmonyOS 那部分 |
| `CLASSIC_DEVICES` | `readonly PresetDeviceProfile[]` | 手选的不到 20 台,同一批对象,给短列表用 |
| `CLASSIC_DEVICES` | `readonly PresetDeviceProfile[]` | 手选的 17 台,同一批对象,给短列表用 |
| `DEFAULT_DEVICE` | `PresetDeviceProfile` | 没指定机型时画的那台(iPhone X) |
| `PLATFORM_DEFAULTS` | `Record<DeviceOS, {...}>` | 各平台的状态栏、导航栏和机身默认值 |
| `DEVICE_NAMES` | `{ [key: string]: string }`(as const) | 每个 `DEVICES[number].name`,键是 `deviceNameKey(name)`——生成文件,见下 |
Expand Down Expand Up @@ -198,6 +222,7 @@ import { deviceUserAgent, systemVersion } from '@devicekit/devices'
| `DeviceShell` | 机身:`screenRadius`、统一的 `bezel`;`bezelInsets` 可单独指定四边,`homeButton` 描述实体 Home 键;用了分边 inset 时应显式设置 `bodyRadius` |
| `CornerRadii` | `{ topLeft, topRight, bottomRight, bottomLeft }`,顺序和 CSS `border-radius` 一致 |
| `screenCorners`(`DeviceShell` 上的字段) | 按角覆盖 `screenRadius`,用于四角屏幕圆角不一致的机身——比如折叠屏合页那一侧。省略的角沿用统一的 `screenRadius` |
| `cornerCurve`(`DeviceShell` 上的字段) | `'circular'` 或 `'continuous'`;只作用在 `.screen`(iOS 默认 continuous / squircle;安卓与鸿蒙保持 circular) |
| `HomeButtonSpec` | `{ diameter }`,机身 bezel 中央的实体 Home 键 |
| `ResolvedDeviceShell` | 四边都已补齐、`homeButton` 为规格或 `null` 的机身数据 |
| `WindowSizeOptions` | `resolveWindowSize` 的选项:`orientation`、`navigationBar`、`tabBarHeight` |
Expand Down
Loading
Loading