diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2cc6bfa..42bc736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: - run: pnpm install --frozen-lockfile + - run: pnpm format:check + - run: pnpm build - run: pnpm lint diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc new file mode 100644 index 0000000..e24f645 --- /dev/null +++ b/.oxfmtrc.jsonc @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "singleQuote": true, + "semi": false, + "trailingComma": "all", +} diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc new file mode 100644 index 0000000..7eabce1 --- /dev/null +++ b/.oxlintrc.jsonc @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "ignorePatterns": [ + "**/coverage/**", + "**/dist/**", + ], +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 128e5b0..e87b0da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `@devicekit/devices`: bundled profiles now include `releaseYear`, and the preset exports use `PresetDeviceProfile` so selectors can sort models chronologically without handling a missing year. + ## [0.2.3] - 2026-09-08 ### Fixed diff --git a/README.md b/README.md index 573c8c1..7c352d4 100644 --- a/README.md +++ b/README.md @@ -66,20 +66,20 @@ The package READMEs contain the full API references. 171 profiles: 63 iOS, 86 Android, 22 HarmonyOS. Folding phones get one entry per screen. -| Manufacturer | Devices | -| --- | ---: | -| Apple | 63 | -| Samsung | 42 | -| Google | 34 | -| Huawei | 22 | -| Motorola | 3 | -| Microsoft | 2 | -| OnePlus | 2 | -| LG | 1 | -| Nothing | 1 | -| Xiaomi | 1 | - -Manufacturer counts are derived from device names because profiles do not have a manufacturer field. `CLASSIC_DEVICES` contains 19 commonly used profiles for shorter selectors. +| Manufacturer | Profiles | Representative models | +| --- | ---: | --- | +| Apple | 63 | iPhone 17 Pro, iPhone 16 Pro, iPhone X, iPad Pro M4 | +| Samsung | 42 | Galaxy S24 Ultra, Galaxy Z Fold 7, Galaxy Z Flip 7 | +| Google | 34 | Pixel 10 Pro, Pixel 9 Pro Fold, Pixel Tablet | +| Huawei | 22 | HUAWEI Mate 80, HUAWEI Pura 80 Pro, HUAWEI Mate X6 (inner), HUAWEI Mate X6 (outer) | +| Motorola | 3 | Motorola Razr+, Moto G4, Moto G Power | +| Microsoft | 2 | Surface Duo | +| OnePlus | 2 | OnePlus 12, OnePlus Open | +| LG | 1 | LG Optimus L70 | +| Nothing | 1 | Nothing Phone 2 | +| Xiaomi | 1 | Xiaomi 14 | + +Every preset includes a `releaseYear`, so a picker can sort models chronologically. Manufacturer counts are derived from device names because profiles do not have a manufacturer field. `CLASSIC_DEVICES` contains 19 commonly used profiles for shorter selectors. ## Browser and framework support diff --git a/README.zh-CN.md b/README.zh-CN.md index d523fbe..f8ff2ef 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -66,20 +66,20 @@ defineDeviceFrame() 共 171 个机型:iOS 63、Android 86、HarmonyOS 22。折叠机内外屏各算一条。 -| 厂商 | 数量 | -| --- | ---: | -| Apple | 63 | -| Samsung | 42 | -| Google | 34 | -| Huawei | 22 | -| Motorola | 3 | -| Microsoft | 2 | -| OnePlus | 2 | -| LG | 1 | -| Nothing | 1 | -| Xiaomi | 1 | - -机型数据没有厂商字段,因此厂商数量按机型名称统计。`CLASSIC_DEVICES` 另提供 19 个常用机型,适合空间较小的选择器。 +| 厂商 | 数量 | 代表机型 | +| --- | ---: | --- | +| Apple | 63 | iPhone 17 Pro、iPhone 16 Pro、iPhone X、iPad Pro M4 | +| Samsung | 42 | Galaxy S24 Ultra、Galaxy Z Fold 7、Galaxy Z Flip 7 | +| Google | 34 | Pixel 10 Pro、Pixel 9 Pro Fold、Pixel Tablet | +| Huawei | 22 | HUAWEI Mate 80、HUAWEI Pura 80 Pro、HUAWEI Mate X6 (inner)、HUAWEI Mate X6 (outer) | +| Motorola | 3 | Motorola Razr+、Moto G4、Moto G Power | +| Microsoft | 2 | Surface Duo | +| OnePlus | 2 | OnePlus 12、OnePlus Open | +| LG | 1 | LG Optimus L70 | +| Nothing | 1 | Nothing Phone 2 | +| Xiaomi | 1 | Xiaomi 14 | + +每个预设都有 `releaseYear`,机型选择器可以按年份排序。机型数据没有厂商字段,因此厂商数量按机型名称统计。`CLASSIC_DEVICES` 另提供 19 个常用机型,适合空间较小的选择器。 ## 浏览器和框架支持 diff --git a/package.json b/package.json index 3eabfbb..85c4b5b 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,20 @@ "scripts": { "build": "pnpm -r build", "check-types": "pnpm -r check-types", + "format": "oxfmt packages/devices/src/presets/ios.ts packages/devices/src/presets/android.ts packages/devices/src/presets/harmony.ts", + "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", "test:coverage": "pnpm -r test:coverage", - "lint": "pnpm -r lint", + "lint": "pnpm -r lint && pnpm lint:oxc", + "lint:oxc": "oxlint .", "verify:pack": "pnpm -r build && node scripts/verify-pack.mjs", "images": "node scripts/render-readme-images.mjs" }, "devDependencies": { "eslint": "^10.2.1", + "oxfmt": "0.67.0", + "oxlint": "1.82.0", "typescript": "5.9.2", "typescript-eslint": "^8.58.2" }, diff --git a/packages/devices/README.md b/packages/devices/README.md index 5f61d39..7df6786 100644 --- a/packages/devices/README.md +++ b/packages/devices/README.md @@ -61,6 +61,7 @@ const profile = { os: 'ios', screen: { width: 402, height: 874 }, pixelRatio: 3, + releaseYear: 2024, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, @@ -70,7 +71,7 @@ const profile = { } ``` -Only `name`, `os`, `screen`, and `pixelRatio` are required. `screen` uses CSS pixels and is stored in portrait orientation. `orientedScreen()` swaps its dimensions for landscape. +Only `name`, `os`, `screen`, and `pixelRatio` are required. Presets also include `releaseYear`, which is the model's release year and can be used to sort a picker. `screen` uses CSS pixels and is stored in portrait orientation. `orientedScreen()` swaps its dimensions for landscape. Orientation-specific status bars, navigation bars, and safe-area insets are stored separately. The status bar height and the top safe-area inset are separate fields because they can differ on devices with a cutout. `cutout` describes the rendered shape; safe-area fields describe the space available to content. @@ -120,12 +121,12 @@ const version = systemVersion(profile) | Export | Type | Description | | --- | --- | --- | -| `DEVICES` | `readonly DeviceProfile[]` | Complete table in iOS, Android, HarmonyOS order | -| `IOS_DEVICES` | `readonly DeviceProfile[]` | iOS profiles | -| `ANDROID_DEVICES` | `readonly DeviceProfile[]` | Android profiles | -| `HARMONY_DEVICES` | `readonly DeviceProfile[]` | HarmonyOS profiles | -| `CLASSIC_DEVICES` | `readonly DeviceProfile[]` | 19 profiles for short selectors | -| `DEFAULT_DEVICE` | `DeviceProfile` | iPhone X profile | +| `DEVICES` | `readonly PresetDeviceProfile[]` | Complete table in iOS, Android, HarmonyOS order | +| `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 | +| `DEFAULT_DEVICE` | `PresetDeviceProfile` | iPhone X profile | | `PLATFORM_DEFAULTS` | `Record` | Default bar and shell values by platform | | `DEVICE_NAMES` | `const object` | Typed name constants generated from `DEVICES` | @@ -154,6 +155,7 @@ const version = systemVersion(profile) | Type | Description | | --- | --- | | `DeviceProfile` | A stored profile with optional device-specific fields | +| `PresetDeviceProfile` | A bundled profile with a required `releaseYear` | | `ResolvedDevice` | A profile with defaults applied | | `DeviceOS` | `'ios' \| 'android' \| 'harmony'` | | `DeviceFormFactor` | `'phone' \| 'tablet'` | diff --git a/packages/devices/README.zh-CN.md b/packages/devices/README.zh-CN.md index e5529f4..6a7ac97 100644 --- a/packages/devices/README.zh-CN.md +++ b/packages/devices/README.zh-CN.md @@ -36,6 +36,7 @@ const device = findDevice(DEVICE_NAMES.iPhone_16_Pro) ?? DEFAULT_DEVICE os: 'ios', screen: { width: 402, height: 874 }, // 物理屏,竖屏方向 pixelRatio: 3, + releaseYear: 2024, // 机型发布年份 system: 'iOS 18.5', statusBarHeight: 54, // 画出来的那条状态栏 safeAreaInsets: { top: 62, bottom: 34 }, // 竖屏,实测值 @@ -149,12 +150,12 @@ import { deviceUserAgent, systemVersion } from '@devicekit/devices' | 导出 | 类型 | 是什么 | | --- | --- | --- | -| `DEVICES` | `readonly DeviceProfile[]` | 整张表,iOS、Android、HarmonyOS 顺序 | -| `IOS_DEVICES` | `readonly DeviceProfile[]` | iOS 那部分 | -| `ANDROID_DEVICES` | `readonly DeviceProfile[]` | Android 那部分 | -| `HARMONY_DEVICES` | `readonly DeviceProfile[]` | HarmonyOS 那部分 | -| `CLASSIC_DEVICES` | `readonly DeviceProfile[]` | 手选的不到 20 台,同一批对象,给短列表用 | -| `DEFAULT_DEVICE` | `DeviceProfile` | 没指定机型时画的那台(iPhone X) | +| `DEVICES` | `readonly PresetDeviceProfile[]` | 整张表,iOS、Android、HarmonyOS 顺序 | +| `IOS_DEVICES` | `readonly PresetDeviceProfile[]` | iOS 那部分 | +| `ANDROID_DEVICES` | `readonly PresetDeviceProfile[]` | Android 那部分 | +| `HARMONY_DEVICES` | `readonly PresetDeviceProfile[]` | HarmonyOS 那部分 | +| `CLASSIC_DEVICES` | `readonly PresetDeviceProfile[]` | 手选的不到 20 台,同一批对象,给短列表用 | +| `DEFAULT_DEVICE` | `PresetDeviceProfile` | 没指定机型时画的那台(iPhone X) | | `PLATFORM_DEFAULTS` | `Record` | 各平台的状态栏、导航栏和机身默认值 | | `DEVICE_NAMES` | `{ [key: string]: string }`(as const) | 每个 `DEVICES[number].name`,键是 `deviceNameKey(name)`——生成文件,见下 | diff --git a/packages/devices/src/devices.ts b/packages/devices/src/devices.ts index f494563..7f15043 100644 --- a/packages/devices/src/devices.ts +++ b/packages/devices/src/devices.ts @@ -118,6 +118,8 @@ export interface DeviceProfile { formFactor?: DeviceFormFactor /** Shown in the device picker, e.g. "iOS 18.0". Also feeds the generated user agent. */ system?: string + /** Calendar year in which this model was released. */ + releaseYear?: number /** * What a page emulating this device should report as `navigator.userAgent`. * Omitted = generated from `os` and `system` — see deviceUserAgent(). @@ -148,6 +150,11 @@ export interface DeviceProfile { shell?: Partial } +/** A bundled profile, whose release year is known and always present. */ +export interface PresetDeviceProfile extends DeviceProfile { + releaseYear: number +} + /** * Fallbacks for a profile that leaves a field out, and for hosts that only know * the platform. @@ -200,6 +207,7 @@ export interface ResolvedDevice { pixelRatio: number formFactor: DeviceFormFactor system: string + releaseYear?: number userAgent: string statusBarHeight: number statusBarHeightLandscape: number @@ -254,6 +262,7 @@ export function resolveDevice(profile: DeviceProfile): ResolvedDevice { pixelRatio: profile.pixelRatio, formFactor: profile.formFactor ?? 'phone', system: profile.system ?? '', + ...(profile.releaseYear === undefined ? {} : { releaseYear: profile.releaseYear }), userAgent: profile.userAgent ?? deviceUserAgent(profile), statusBarHeight, statusBarHeightLandscape, diff --git a/packages/devices/src/index.ts b/packages/devices/src/index.ts index 937e7f0..6a7b05e 100644 --- a/packages/devices/src/index.ts +++ b/packages/devices/src/index.ts @@ -9,6 +9,7 @@ export { type DeviceFormFactor, type DeviceOS, type DeviceProfile, + type PresetDeviceProfile, type DeviceShell, type EdgeInsets, type HomeButtonSpec, diff --git a/packages/devices/src/presets.test.ts b/packages/devices/src/presets.test.ts index 7a29b8b..a35765d 100644 --- a/packages/devices/src/presets.test.ts +++ b/packages/devices/src/presets.test.ts @@ -41,6 +41,11 @@ describe('the table is well formed', () => { expect(broken.map((d) => d.name)).toEqual([]) }) + it('records the release year for every device so consumers can sort the table chronologically', () => { + const missingOrInvalid = DEVICES.filter((d) => !Number.isInteger(d.releaseYear) || d.releaseYear! < 2000 || d.releaseYear! > 2100) + expect(missingOrInvalid.map((d) => d.name)).toEqual([]) + }) + // Rotating is what the orientation argument is for. A source table that also // ships a pre-rotated copy of a device leaves two rows here where one turns // into the other, and the pre-rotated one then rotates backwards. diff --git a/packages/devices/src/presets/android.ts b/packages/devices/src/presets/android.ts index ff48f6e..8a3a059 100644 --- a/packages/devices/src/presets/android.ts +++ b/packages/devices/src/presets/android.ts @@ -4,96 +4,892 @@ * One line per device. Omitted fields fall back to the platform defaults in * devices.ts — see the README for what each source could and could not tell us. */ -import type { DeviceProfile } from '../devices.js' +import type { PresetDeviceProfile } from '../devices.js' /** Android phones and tablets. Part of DEVICES; listed here so a host can offer one platform alone. */ -export const ANDROID_DEVICES: readonly DeviceProfile[] = [ - { name: 'Pixel 7', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 13', statusBarHeight: 52, safeAreaInsets: { top: 52 }, safeAreaInsetsLandscape: { right: 52 }, cutout: { shape: 'circle', width: 26, height: 26, top: 13 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 8', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', statusBarHeight: 50, safeAreaInsets: { top: 50 }, safeAreaInsetsLandscape: { right: 50 }, cutout: { shape: 'circle', width: 28, height: 28, top: 11 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 8 Pro', os: 'android', screen: { width: 448, height: 997 }, pixelRatio: 3, system: 'Android 14', statusBarHeight: 50, safeAreaInsets: { top: 50 }, safeAreaInsetsLandscape: { right: 50 }, cutout: { shape: 'circle', width: 28, height: 28, top: 11 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 8a', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', statusBarHeight: 46, safeAreaInsets: { top: 46 }, safeAreaInsetsLandscape: { right: 46 }, cutout: { shape: 'circle', width: 26, height: 26, top: 13 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 9', os: 'android', screen: { width: 412, height: 924 }, pixelRatio: 2.625, system: 'Android 14', statusBarHeight: 58, safeAreaInsets: { top: 58 }, safeAreaInsetsLandscape: { right: 58 }, cutout: { shape: 'circle', width: 28, height: 28, top: 15 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 9 Pro', os: 'android', screen: { width: 427, height: 952 }, pixelRatio: 3, system: 'Android 14', statusBarHeight: 68, safeAreaInsets: { top: 68 }, safeAreaInsetsLandscape: { right: 68 }, cutout: { shape: 'circle', width: 32, height: 32, top: 18 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 9 Pro XL', os: 'android', screen: { width: 448, height: 997 }, pixelRatio: 3, system: 'Android 14', statusBarHeight: 66, safeAreaInsets: { top: 66 }, safeAreaInsetsLandscape: { right: 66 }, cutout: { shape: 'circle', width: 32, height: 32, top: 17 }, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 10', os: 'android', screen: { width: 412, height: 924 }, pixelRatio: 2.625, system: 'Android 16', statusBarHeight: 58, safeAreaInsets: { top: 58 }, safeAreaInsetsLandscape: { right: 58 }, cutout: { shape: 'circle', width: 28, height: 28, top: 15 }, userAgent: 'Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Samsung Galaxy S8+', os: 'android', screen: { width: 360, height: 740 }, pixelRatio: 4, system: 'Android 7.0', userAgent: 'Mozilla/5.0 (Linux; Android 7.0; SM-G955U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Samsung Galaxy S20 Ultra', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 3.5, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36' }, - { name: 'Surface Duo', os: 'android', screen: { width: 540, height: 720 }, pixelRatio: 2.5, system: 'Android 11.0' }, +export const ANDROID_DEVICES: readonly PresetDeviceProfile[] = [ + { + name: 'Pixel 7', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 13', + statusBarHeight: 52, + safeAreaInsets: { top: 52 }, + safeAreaInsetsLandscape: { right: 52 }, + cutout: { shape: 'circle', width: 26, height: 26, top: 13 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Pixel 8', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + statusBarHeight: 50, + safeAreaInsets: { top: 50 }, + safeAreaInsetsLandscape: { right: 50 }, + cutout: { shape: 'circle', width: 28, height: 28, top: 11 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Pixel 8 Pro', + os: 'android', + screen: { width: 448, height: 997 }, + pixelRatio: 3, + system: 'Android 14', + statusBarHeight: 50, + safeAreaInsets: { top: 50 }, + safeAreaInsetsLandscape: { right: 50 }, + cutout: { shape: 'circle', width: 28, height: 28, top: 11 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Pixel 8a', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + statusBarHeight: 46, + safeAreaInsets: { top: 46 }, + safeAreaInsetsLandscape: { right: 46 }, + cutout: { shape: 'circle', width: 26, height: 26, top: 13 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 9', + os: 'android', + screen: { width: 412, height: 924 }, + pixelRatio: 2.625, + system: 'Android 14', + statusBarHeight: 58, + safeAreaInsets: { top: 58 }, + safeAreaInsetsLandscape: { right: 58 }, + cutout: { shape: 'circle', width: 28, height: 28, top: 15 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 9 Pro', + os: 'android', + screen: { width: 427, height: 952 }, + pixelRatio: 3, + system: 'Android 14', + statusBarHeight: 68, + safeAreaInsets: { top: 68 }, + safeAreaInsetsLandscape: { right: 68 }, + cutout: { shape: 'circle', width: 32, height: 32, top: 18 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 9 Pro XL', + os: 'android', + screen: { width: 448, height: 997 }, + pixelRatio: 3, + system: 'Android 14', + statusBarHeight: 66, + safeAreaInsets: { top: 66 }, + safeAreaInsetsLandscape: { right: 66 }, + cutout: { shape: 'circle', width: 32, height: 32, top: 17 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 10', + os: 'android', + screen: { width: 412, height: 924 }, + pixelRatio: 2.625, + system: 'Android 16', + statusBarHeight: 58, + safeAreaInsets: { top: 58 }, + safeAreaInsetsLandscape: { right: 58 }, + cutout: { shape: 'circle', width: 28, height: 28, top: 15 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Samsung Galaxy S8+', + os: 'android', + screen: { width: 360, height: 740 }, + pixelRatio: 4, + system: 'Android 7.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 7.0; SM-G955U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2017, + }, + { + name: 'Samsung Galaxy S20 Ultra', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 3.5, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36', + releaseYear: 2020, + }, + { + name: 'Surface Duo', + os: 'android', + screen: { width: 540, height: 720 }, + pixelRatio: 2.5, + system: 'Android 11.0', + releaseYear: 2020, + }, // Table stores portrait; the unfolded (wider-than-tall) screen is what orientation=landscape reads out. - { name: 'Surface Duo (inner)', os: 'android', screen: { width: 720, height: 1114 }, pixelRatio: 2.5, system: 'Android 11.0' }, - { name: 'Samsung Galaxy A55', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 2.25, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 9 Pro Fold', os: 'android', screen: { width: 412, height: 922 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36' }, - { name: 'Pixel 9 Pro Fold (inner)', os: 'android', screen: { width: 836, height: 842 }, pixelRatio: 2.625, system: 'Android 14' }, - { name: 'Galaxy Z Fold 6', os: 'android', screen: { width: 412, height: 968 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, + { + name: 'Surface Duo (inner)', + os: 'android', + screen: { width: 720, height: 1114 }, + pixelRatio: 2.5, + system: 'Android 11.0', + releaseYear: 2020, + }, + { + name: 'Samsung Galaxy A55', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 2.25, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 9 Pro Fold', + os: 'android', + screen: { width: 412, height: 922 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Pixel 9 Pro Fold (inner)', + os: 'android', + screen: { width: 836, height: 842 }, + pixelRatio: 2.625, + system: 'Android 14', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Fold 6', + os: 'android', + screen: { width: 412, height: 968 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, // 707 x 823 @ 2.625 reproduces the panel's 1856 x 2160 physical pixels (Samsung's spec sheet) within 1 px, the same density the Fold 5 inner row uses. - { name: 'Galaxy Z Fold 6 (inner)', os: 'android', screen: { width: 707, height: 823 }, pixelRatio: 2.625, system: 'Android 14' }, - { name: 'Galaxy Z Fold 5', os: 'android', screen: { width: 344, height: 882 }, pixelRatio: 2.625, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-F946B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'Galaxy Z Fold 5 (inner)', os: 'android', screen: { width: 690, height: 829 }, pixelRatio: 2.625, system: 'Android 13' }, - { name: 'Samsung Galaxy A51/71', os: 'android', screen: { width: 412, height: 914 }, pixelRatio: 2.625, system: 'Android 10', userAgent: 'Mozilla/5.0 (Linux; Android 10; SM-A515F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36' }, - { name: 'Galaxy Tab S4', os: 'android', formFactor: 'tablet', screen: { width: 712, height: 1138 }, pixelRatio: 2.25, system: 'Android 8.1', userAgent: 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36' }, - { name: 'Moto G4', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 3, system: 'Android 6.0', userAgent: 'Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Moto G Power', os: 'android', screen: { width: 412, height: 823 }, pixelRatio: 1.75, system: 'Android 11' }, - { name: 'Nexus 5', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 3, system: 'Android 6.0', shell: { screenRadius: 2 }, userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36' }, - { name: 'Nexus 5X', os: 'android', screen: { width: 411, height: 731 }, pixelRatio: 2.625, system: 'Android 8.0', shell: { screenRadius: 2 }, userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36' }, - { name: 'Nexus 6', os: 'android', screen: { width: 412, height: 732 }, pixelRatio: 3.5, system: 'Android 7.1', shell: { screenRadius: 2 }, userAgent: 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36' }, - { name: 'Galaxy Note 3', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 3, system: 'Android 4.3', userAgent: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' }, - { name: 'Galaxy Note II', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 2, system: 'Android 4.1', userAgent: 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' }, - { name: 'Galaxy S III', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 2, system: 'Android 4.0', userAgent: 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' }, - { name: 'Galaxy S5', os: 'android', screen: { width: 360, height: 640 }, pixelRatio: 3, system: 'Android 5.0', userAgent: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy S9+', os: 'android', screen: { width: 320, height: 658 }, pixelRatio: 4.5, system: 'Android 8.0', userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy S24', os: 'android', screen: { width: 360, height: 780 }, pixelRatio: 3, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Tab S9', os: 'android', formFactor: 'tablet', screen: { width: 640, height: 1024 }, pixelRatio: 2.5, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36' }, - { name: 'Galaxy Z Fold 6 Cover', os: 'android', screen: { width: 484, height: 1188 }, pixelRatio: 2, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Fold 7', os: 'android', screen: { width: 984, height: 1092 }, pixelRatio: 2, system: 'Android 16', userAgent: 'Mozilla/5.0 (Linux; Android 16; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Fold 7 Cover', os: 'android', screen: { width: 360, height: 840 }, pixelRatio: 3, system: 'Android 16', userAgent: 'Mozilla/5.0 (Linux; Android 16; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Flip 6', os: 'android', screen: { width: 360, height: 880 }, pixelRatio: 3, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Flip 6 Cover', os: 'android', screen: { width: 360, height: 374 }, pixelRatio: 2, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Flip 7', os: 'android', screen: { width: 360, height: 840 }, pixelRatio: 3, system: 'Android 16', userAgent: 'Mozilla/5.0 (Linux; Android 16; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Galaxy Z Flip 7 Cover', os: 'android', screen: { width: 474, height: 524 }, pixelRatio: 2, system: 'Android 16', userAgent: 'Mozilla/5.0 (Linux; Android 16; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'LG Optimus L70', os: 'android', screen: { width: 384, height: 640 }, pixelRatio: 1.25, system: 'Android 4.4', userAgent: 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Nexus 10', os: 'android', formFactor: 'tablet', screen: { width: 800, height: 1280 }, pixelRatio: 2, system: 'Android 5.1', userAgent: 'Mozilla/5.0 (Linux; Android 5.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36' }, - { name: 'Nexus 4', os: 'android', screen: { width: 384, height: 640 }, pixelRatio: 2, system: 'Android 4.4', userAgent: 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Nexus 6P', os: 'android', screen: { width: 412, height: 732 }, pixelRatio: 3.5, system: 'Android 8.0', userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Nexus 7', os: 'android', formFactor: 'tablet', screen: { width: 600, height: 960 }, pixelRatio: 2, system: 'Android 6.0', userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36' }, - { name: 'Pixel 2', os: 'android', screen: { width: 411, height: 731 }, pixelRatio: 2.625, system: 'Android 8.0', userAgent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 2 XL', os: 'android', screen: { width: 411, height: 823 }, pixelRatio: 3.5, system: 'Android 8.0', userAgent: 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 3', os: 'android', screen: { width: 393, height: 786 }, pixelRatio: 2.75, system: 'Android 9', userAgent: 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 4', os: 'android', screen: { width: 353, height: 745 }, pixelRatio: 3, system: 'Android 10', userAgent: 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 4a (5G)', os: 'android', screen: { width: 412, height: 892 }, pixelRatio: 2.63, system: 'Android 11', userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 5', os: 'android', screen: { width: 393, height: 851 }, pixelRatio: 2.75, system: 'Android 11', userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 6', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 12', userAgent: 'Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 6 Pro', os: 'android', screen: { width: 412, height: 892 }, pixelRatio: 3.5, system: 'Android 12', userAgent: 'Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 6a', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 12', userAgent: 'Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 7 Pro', os: 'android', screen: { width: 412, height: 892 }, pixelRatio: 3.5, system: 'Android 13', statusBarHeight: 41, cutout: { shape: 'circle', width: 25, height: 25, top: 8 }, userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 7a', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 10 Pro', os: 'android', screen: { width: 427, height: 952 }, pixelRatio: 3, system: 'Android 16', statusBarHeight: 68, safeAreaInsets: { top: 68 }, safeAreaInsetsLandscape: { right: 68 }, cutout: { shape: 'circle', width: 32, height: 32, top: 18 }, userAgent: 'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 10 Pro XL', os: 'android', screen: { width: 448, height: 997 }, pixelRatio: 3, system: 'Android 16', userAgent: 'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36' }, - { name: 'Pixel 3 XL', os: 'android', screen: { width: 393, height: 786 }, pixelRatio: 2.75, system: 'Android 11', userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.181 Mobile Safari/537.36' }, - { name: 'Pixel Fold', os: 'android', screen: { width: 734, height: 1014 }, pixelRatio: 2.8, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36' }, - { name: 'Pixel Tablet', os: 'android', formFactor: 'tablet', screen: { width: 800, height: 1280 }, pixelRatio: 2, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel Tablet) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' }, - { name: 'Pixel 9a', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 15', userAgent: 'Mozilla/5.0 (Linux; Android 15; Pixel 9a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy Fold', os: 'android', screen: { width: 280, height: 653 }, pixelRatio: 3, system: 'Android 9.0' }, - { name: 'Galaxy S21', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 3, system: 'Android 11' }, - { name: 'Galaxy S21 Plus', os: 'android', screen: { width: 384, height: 854 }, pixelRatio: 2.8125, system: 'Android 11' }, - { name: 'Galaxy S21 Ultra', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 11' }, - { name: 'Galaxy S20', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 3, system: 'Android 10' }, - { name: 'Galaxy S20 Plus', os: 'android', screen: { width: 384, height: 854 }, pixelRatio: 3, system: 'Android 10' }, - { name: 'Samsung S21 FE', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 3, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-G990B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/20.0 Chrome/106.0.5249.126 Mobile Safari/537.36' }, - { name: 'Galaxy Fold3 (Folded)', os: 'android', screen: { width: 320, height: 872 }, pixelRatio: 3, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'Galaxy Fold3 (Unfolded)', os: 'android', screen: { width: 590, height: 736 }, pixelRatio: 3, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'Galaxy S22', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 3, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S22 Plus', os: 'android', screen: { width: 384, height: 854 }, pixelRatio: 3, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S906B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S22 Ultra', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S23', os: 'android', screen: { width: 360, height: 800 }, pixelRatio: 3, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S23 Plus', os: 'android', screen: { width: 384, height: 854 }, pixelRatio: 3, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S23 Ultra', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S24 Plus', os: 'android', screen: { width: 384, height: 854 }, pixelRatio: 3, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S926B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy S24 Ultra', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Galaxy Z Flip 5', os: 'android', screen: { width: 264, height: 844 }, pixelRatio: 2.8, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-F731B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'Galaxy Z Fold 4', os: 'android', screen: { width: 344, height: 882 }, pixelRatio: 2.8, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-F936B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'OnePlus 12', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; CPH2581) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Nothing Phone 2', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; A065) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' }, - { name: 'Xiaomi 14', os: 'android', screen: { width: 412, height: 915 }, pixelRatio: 2.625, system: 'Android 14', userAgent: 'Mozilla/5.0 (Linux; Android 14; 23127PN0CC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36' }, - { name: 'Motorola Razr+', os: 'android', screen: { width: 264, height: 844 }, pixelRatio: 2.8, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; XT2321-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, - { name: 'OnePlus Open', os: 'android', screen: { width: 734, height: 1014 }, pixelRatio: 2.8, system: 'Android 13', userAgent: 'Mozilla/5.0 (Linux; Android 13; CPH2551) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36' }, + { + name: 'Galaxy Z Fold 6 (inner)', + os: 'android', + screen: { width: 707, height: 823 }, + pixelRatio: 2.625, + system: 'Android 14', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Fold 5', + os: 'android', + screen: { width: 344, height: 882 }, + pixelRatio: 2.625, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F946B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy Z Fold 5 (inner)', + os: 'android', + screen: { width: 690, height: 829 }, + pixelRatio: 2.625, + system: 'Android 13', + releaseYear: 2023, + }, + { + name: 'Samsung Galaxy A51/71', + os: 'android', + screen: { width: 412, height: 914 }, + pixelRatio: 2.625, + system: 'Android 10', + userAgent: + 'Mozilla/5.0 (Linux; Android 10; SM-A515F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36', + releaseYear: 2019, + }, + { + name: 'Galaxy Tab S4', + os: 'android', + formFactor: 'tablet', + screen: { width: 712, height: 1138 }, + pixelRatio: 2.25, + system: 'Android 8.1', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36', + releaseYear: 2018, + }, + { + name: 'Moto G4', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 3, + system: 'Android 6.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2016, + }, + { + name: 'Moto G Power', + os: 'android', + screen: { width: 412, height: 823 }, + pixelRatio: 1.75, + system: 'Android 11', + releaseYear: 2021, + }, + { + name: 'Nexus 5', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 3, + system: 'Android 6.0', + shell: { screenRadius: 2 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36', + releaseYear: 2013, + }, + { + name: 'Nexus 5X', + os: 'android', + screen: { width: 411, height: 731 }, + pixelRatio: 2.625, + system: 'Android 8.0', + shell: { screenRadius: 2 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36', + releaseYear: 2015, + }, + { + name: 'Nexus 6', + os: 'android', + screen: { width: 412, height: 732 }, + pixelRatio: 3.5, + system: 'Android 7.1', + shell: { screenRadius: 2 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36', + releaseYear: 2014, + }, + { + name: 'Galaxy Note 3', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 3, + system: 'Android 4.3', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + releaseYear: 2013, + }, + { + name: 'Galaxy Note II', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 2, + system: 'Android 4.1', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + releaseYear: 2012, + }, + { + name: 'Galaxy S III', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 2, + system: 'Android 4.0', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + releaseYear: 2012, + }, + { + name: 'Galaxy S5', + os: 'android', + screen: { width: 360, height: 640 }, + pixelRatio: 3, + system: 'Android 5.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2014, + }, + { + name: 'Galaxy S9+', + os: 'android', + screen: { width: 320, height: 658 }, + pixelRatio: 4.5, + system: 'Android 8.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2018, + }, + { + name: 'Galaxy S24', + os: 'android', + screen: { width: 360, height: 780 }, + pixelRatio: 3, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy Tab S9', + os: 'android', + formFactor: 'tablet', + screen: { width: 640, height: 1024 }, + pixelRatio: 2.5, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy Z Fold 6 Cover', + os: 'android', + screen: { width: 484, height: 1188 }, + pixelRatio: 2, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-F956U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Fold 7', + os: 'android', + screen: { width: 984, height: 1092 }, + pixelRatio: 2, + system: 'Android 16', + userAgent: + 'Mozilla/5.0 (Linux; Android 16; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Galaxy Z Fold 7 Cover', + os: 'android', + screen: { width: 360, height: 840 }, + pixelRatio: 3, + system: 'Android 16', + userAgent: + 'Mozilla/5.0 (Linux; Android 16; SM-F966U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Galaxy Z Flip 6', + os: 'android', + screen: { width: 360, height: 880 }, + pixelRatio: 3, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Flip 6 Cover', + os: 'android', + screen: { width: 360, height: 374 }, + pixelRatio: 2, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-F741U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Flip 7', + os: 'android', + screen: { width: 360, height: 840 }, + pixelRatio: 3, + system: 'Android 16', + userAgent: + 'Mozilla/5.0 (Linux; Android 16; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Galaxy Z Flip 7 Cover', + os: 'android', + screen: { width: 474, height: 524 }, + pixelRatio: 2, + system: 'Android 16', + userAgent: + 'Mozilla/5.0 (Linux; Android 16; SM-F761U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'LG Optimus L70', + os: 'android', + screen: { width: 384, height: 640 }, + pixelRatio: 1.25, + system: 'Android 4.4', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2014, + }, + { + name: 'Nexus 10', + os: 'android', + formFactor: 'tablet', + screen: { width: 800, height: 1280 }, + pixelRatio: 2, + system: 'Android 5.1', + userAgent: + 'Mozilla/5.0 (Linux; Android 5.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36', + releaseYear: 2012, + }, + { + name: 'Nexus 4', + os: 'android', + screen: { width: 384, height: 640 }, + pixelRatio: 2, + system: 'Android 4.4', + userAgent: + 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2012, + }, + { + name: 'Nexus 6P', + os: 'android', + screen: { width: 412, height: 732 }, + pixelRatio: 3.5, + system: 'Android 8.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2015, + }, + { + name: 'Nexus 7', + os: 'android', + formFactor: 'tablet', + screen: { width: 600, height: 960 }, + pixelRatio: 2, + system: 'Android 6.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Safari/537.36', + releaseYear: 2012, + }, + { + name: 'Pixel 2', + os: 'android', + screen: { width: 411, height: 731 }, + pixelRatio: 2.625, + system: 'Android 8.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2017, + }, + { + name: 'Pixel 2 XL', + os: 'android', + screen: { width: 411, height: 823 }, + pixelRatio: 3.5, + system: 'Android 8.0', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2017, + }, + { + name: 'Pixel 3', + os: 'android', + screen: { width: 393, height: 786 }, + pixelRatio: 2.75, + system: 'Android 9', + userAgent: + 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2018, + }, + { + name: 'Pixel 4', + os: 'android', + screen: { width: 353, height: 745 }, + pixelRatio: 3, + system: 'Android 10', + userAgent: + 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2019, + }, + { + name: 'Pixel 4a (5G)', + os: 'android', + screen: { width: 412, height: 892 }, + pixelRatio: 2.63, + system: 'Android 11', + userAgent: + 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2020, + }, + { + name: 'Pixel 5', + os: 'android', + screen: { width: 393, height: 851 }, + pixelRatio: 2.75, + system: 'Android 11', + userAgent: + 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2020, + }, + { + name: 'Pixel 6', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 12', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2021, + }, + { + name: 'Pixel 6 Pro', + os: 'android', + screen: { width: 412, height: 892 }, + pixelRatio: 3.5, + system: 'Android 12', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2021, + }, + { + name: 'Pixel 6a', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 12', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; Pixel 6a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Pixel 7 Pro', + os: 'android', + screen: { width: 412, height: 892 }, + pixelRatio: 3.5, + system: 'Android 13', + statusBarHeight: 41, + cutout: { shape: 'circle', width: 25, height: 25, top: 8 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Pixel 7a', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Pixel 10 Pro', + os: 'android', + screen: { width: 427, height: 952 }, + pixelRatio: 3, + system: 'Android 16', + statusBarHeight: 68, + safeAreaInsets: { top: 68 }, + safeAreaInsetsLandscape: { right: 68 }, + cutout: { shape: 'circle', width: 32, height: 32, top: 18 }, + userAgent: + 'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Pixel 10 Pro XL', + os: 'android', + screen: { width: 448, height: 997 }, + pixelRatio: 3, + system: 'Android 16', + userAgent: + 'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.7827.55 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Pixel 3 XL', + os: 'android', + screen: { width: 393, height: 786 }, + pixelRatio: 2.75, + system: 'Android 11', + userAgent: + 'Mozilla/5.0 (Linux; Android 11; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.181 Mobile Safari/537.36', + releaseYear: 2018, + }, + { + name: 'Pixel Fold', + os: 'android', + screen: { width: 734, height: 1014 }, + pixelRatio: 2.8, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; Pixel Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Pixel Tablet', + os: 'android', + formFactor: 'tablet', + screen: { width: 800, height: 1280 }, + pixelRatio: 2, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; Pixel Tablet) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Pixel 9a', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 15', + userAgent: + 'Mozilla/5.0 (Linux; Android 15; Pixel 9a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + releaseYear: 2025, + }, + { + name: 'Galaxy Fold', + os: 'android', + screen: { width: 280, height: 653 }, + pixelRatio: 3, + system: 'Android 9.0', + releaseYear: 2019, + }, + { + name: 'Galaxy S21', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 3, + system: 'Android 11', + releaseYear: 2021, + }, + { + name: 'Galaxy S21 Plus', + os: 'android', + screen: { width: 384, height: 854 }, + pixelRatio: 2.8125, + system: 'Android 11', + releaseYear: 2021, + }, + { + name: 'Galaxy S21 Ultra', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 11', + releaseYear: 2021, + }, + { + name: 'Galaxy S20', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 3, + system: 'Android 10', + releaseYear: 2020, + }, + { + name: 'Galaxy S20 Plus', + os: 'android', + screen: { width: 384, height: 854 }, + pixelRatio: 3, + system: 'Android 10', + releaseYear: 2020, + }, + { + name: 'Samsung S21 FE', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 3, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-G990B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/20.0 Chrome/106.0.5249.126 Mobile Safari/537.36', + releaseYear: 2021, + }, + { + name: 'Galaxy Fold3 (Folded)', + os: 'android', + screen: { width: 320, height: 872 }, + pixelRatio: 3, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2021, + }, + { + name: 'Galaxy Fold3 (Unfolded)', + os: 'android', + screen: { width: 590, height: 736 }, + pixelRatio: 3, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2021, + }, + { + name: 'Galaxy S22', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 3, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Galaxy S22 Plus', + os: 'android', + screen: { width: 384, height: 854 }, + pixelRatio: 3, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-S906B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Galaxy S22 Ultra', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'Galaxy S23', + os: 'android', + screen: { width: 360, height: 800 }, + pixelRatio: 3, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy S23 Plus', + os: 'android', + screen: { width: 384, height: 854 }, + pixelRatio: 3, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy S23 Ultra', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy S24 Plus', + os: 'android', + screen: { width: 384, height: 854 }, + pixelRatio: 3, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S926B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy S24 Ultra', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Galaxy Z Flip 5', + os: 'android', + screen: { width: 264, height: 844 }, + pixelRatio: 2.8, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F731B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Galaxy Z Fold 4', + os: 'android', + screen: { width: 344, height: 882 }, + pixelRatio: 2.8, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F936B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2022, + }, + { + name: 'OnePlus 12', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; CPH2581) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Nothing Phone 2', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; A065) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'Xiaomi 14', + os: 'android', + screen: { width: 412, height: 915 }, + pixelRatio: 2.625, + system: 'Android 14', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; 23127PN0CC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + releaseYear: 2024, + }, + { + name: 'Motorola Razr+', + os: 'android', + screen: { width: 264, height: 844 }, + pixelRatio: 2.8, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; XT2321-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2023, + }, + { + name: 'OnePlus Open', + os: 'android', + screen: { width: 734, height: 1014 }, + pixelRatio: 2.8, + system: 'Android 13', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; CPH2551) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36', + releaseYear: 2023, + }, ] diff --git a/packages/devices/src/presets/classic.ts b/packages/devices/src/presets/classic.ts index b941daa..7885efd 100644 --- a/packages/devices/src/presets/classic.ts +++ b/packages/devices/src/presets/classic.ts @@ -1,4 +1,4 @@ -import type { DeviceProfile } from '../devices.js' +import type { PresetDeviceProfile } from '../devices.js' import { ANDROID_DEVICES } from './android.js' import { HARMONY_DEVICES } from './harmony.js' import { IOS_DEVICES } from './ios.js' @@ -7,7 +7,7 @@ import { IOS_DEVICES } from './ios.js' // DEVICES in ./index.js, so this module has no circular dependency on it. const BY_NAME = new Map([...IOS_DEVICES, ...ANDROID_DEVICES, ...HARMONY_DEVICES].map((device) => [device.name, device])) -function pick(name: string): DeviceProfile { +function pick(name: string): PresetDeviceProfile { const device = BY_NAME.get(name) if (!device) throw new Error(`CLASSIC_DEVICES: no device named ${JSON.stringify(name)} in DEVICES`) return device @@ -19,7 +19,7 @@ function pick(name: string): DeviceProfile { * objects as in the full table (never copies), grouped iOS → Android → * HarmonyOS so a grouped dropdown needs no sorting of its own. */ -export const CLASSIC_DEVICES: readonly DeviceProfile[] = [ +export const CLASSIC_DEVICES: readonly PresetDeviceProfile[] = [ pick('iPhone SE (3rd gen)'), pick('iPhone X'), pick('iPhone 12/13 (Pro)'), diff --git a/packages/devices/src/presets/harmony.ts b/packages/devices/src/presets/harmony.ts index c39588b..0b2c83f 100644 --- a/packages/devices/src/presets/harmony.ts +++ b/packages/devices/src/presets/harmony.ts @@ -4,31 +4,272 @@ * One line per device. Omitted fields fall back to the platform defaults in * devices.ts — see the README for what each source could and could not tell us. */ -import type { DeviceProfile } from '../devices.js' +import type { PresetDeviceProfile } from '../devices.js' /** HarmonyOS phones, including both screens of each folding model. Part of DEVICES; listed here so a host can offer one platform alone. */ -export const HARMONY_DEVICES: readonly DeviceProfile[] = [ - { name: 'HUAWEI Mate 80', os: 'harmony', screen: { width: 366, height: 809 }, pixelRatio: 3.5, system: 'HarmonyOS 5.0', statusBarHeight: 39, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 39 } }, - { name: 'HUAWEI Mate 70', os: 'harmony', screen: { width: 374, height: 827 }, pixelRatio: 3.25, system: 'HarmonyOS 5.0', statusBarHeight: 34, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 34 } }, - { name: 'HUAWEI Mate 70 Pro', os: 'harmony', screen: { width: 376, height: 809 }, pixelRatio: 3.5, system: 'HarmonyOS 5.0', statusBarHeight: 39, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 39 } }, - { name: 'HUAWEI Mate 60', os: 'harmony', screen: { width: 374, height: 827 }, pixelRatio: 3.25, system: 'HarmonyOS 5.0', statusBarHeight: 33, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 33 } }, - { name: 'HUAWEI Mate 60 Pro', os: 'harmony', screen: { width: 388, height: 837 }, pixelRatio: 3.25, system: 'HarmonyOS 5.0', statusBarHeight: 38, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 38 } }, - { name: 'HUAWEI Pura 80 Pro', os: 'harmony', screen: { width: 365, height: 814 }, pixelRatio: 3.5, system: 'HarmonyOS 5.0', statusBarHeight: 38, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 38 } }, - { name: 'HUAWEI nova 14 Ultra', os: 'harmony', screen: { width: 363, height: 817 }, pixelRatio: 3.5, system: 'HarmonyOS 5.0', statusBarHeight: 48, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 48 } }, - { name: 'HUAWEI Pura X (inner)', os: 'harmony', screen: { width: 440, height: 707 }, pixelRatio: 3, system: 'HarmonyOS 5.0', statusBarHeight: 39, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 39 } }, - { name: 'HUAWEI Pura X (outer)', os: 'harmony', screen: { width: 327, height: 327 }, pixelRatio: 3, system: 'HarmonyOS 5.0', statusBarHeight: 0, navigationBarHeight: 0, navigationBarHeightLandscape: 0 }, - { name: 'HUAWEI Mate X6 (inner)', os: 'harmony', screen: { width: 717, height: 781 }, pixelRatio: 3.125, system: 'HarmonyOS 5.0', statusBarHeight: 39, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 39 } }, - { name: 'HUAWEI Mate X6 (outer)', os: 'harmony', screen: { width: 346, height: 781 }, pixelRatio: 3.125, system: 'HarmonyOS 5.0', statusBarHeight: 39, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 39 } }, - { name: 'HUAWEI Pura 70', os: 'harmony', screen: { width: 372, height: 818 }, pixelRatio: 3.375, system: 'HarmonyOS 5.0', statusBarHeight: 36, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 36 } }, - { name: 'HUAWEI Pura 70 Pro', os: 'harmony', screen: { width: 373, height: 843 }, pixelRatio: 3.375, system: 'HarmonyOS 5.0', statusBarHeight: 38, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 38 } }, - { name: 'HUAWEI Mate X5 (inner)', os: 'harmony', screen: { width: 712, height: 799 }, pixelRatio: 3.125, system: 'HarmonyOS 5.0', statusBarHeight: 36, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 36 } }, - { name: 'HUAWEI Mate X5 (outer)', os: 'harmony', screen: { width: 346, height: 801 }, pixelRatio: 3.125, system: 'HarmonyOS 5.0', statusBarHeight: 36, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 36 } }, - { name: 'HUAWEI Pocket 2', os: 'harmony', screen: { width: 364, height: 861 }, pixelRatio: 3.125, system: 'HarmonyOS 5.0', statusBarHeight: 34, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 34 } }, - { name: 'HUAWEI nova 13', os: 'harmony', screen: { width: 361, height: 804 }, pixelRatio: 3, system: 'HarmonyOS 5.0', statusBarHeight: 35, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 35 } }, - { name: 'HUAWEI nova 13 Pro', os: 'harmony', screen: { width: 363, height: 823 }, pixelRatio: 3.375, system: 'HarmonyOS 5.0', statusBarHeight: 44, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 44 } }, - { name: 'HUAWEI nova 12', os: 'harmony', screen: { width: 361, height: 804 }, pixelRatio: 3, system: 'HarmonyOS 5.0', statusBarHeight: 35, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 35 } }, - { name: 'HUAWEI nova 12 Pro', os: 'harmony', screen: { width: 363, height: 823 }, pixelRatio: 3.375, system: 'HarmonyOS 5.0', statusBarHeight: 44, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 44 } }, - { name: 'HUAWEI Pura X Max (outer)', os: 'harmony', screen: { width: 460, height: 672 }, pixelRatio: 2.75, system: 'HarmonyOS 5.0', statusBarHeight: 66, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 66 } }, +export const HARMONY_DEVICES: readonly PresetDeviceProfile[] = [ + { + name: 'HUAWEI Mate 80', + os: 'harmony', + screen: { width: 366, height: 809 }, + pixelRatio: 3.5, + system: 'HarmonyOS 5.0', + statusBarHeight: 39, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 39 }, + releaseYear: 2025, + }, + { + name: 'HUAWEI Mate 70', + os: 'harmony', + screen: { width: 374, height: 827 }, + pixelRatio: 3.25, + system: 'HarmonyOS 5.0', + statusBarHeight: 34, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 34 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Mate 70 Pro', + os: 'harmony', + screen: { width: 376, height: 809 }, + pixelRatio: 3.5, + system: 'HarmonyOS 5.0', + statusBarHeight: 39, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 39 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Mate 60', + os: 'harmony', + screen: { width: 374, height: 827 }, + pixelRatio: 3.25, + system: 'HarmonyOS 5.0', + statusBarHeight: 33, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 33 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI Mate 60 Pro', + os: 'harmony', + screen: { width: 388, height: 837 }, + pixelRatio: 3.25, + system: 'HarmonyOS 5.0', + statusBarHeight: 38, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 38 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI Pura 80 Pro', + os: 'harmony', + screen: { width: 365, height: 814 }, + pixelRatio: 3.5, + system: 'HarmonyOS 5.0', + statusBarHeight: 38, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 38 }, + releaseYear: 2025, + }, + { + name: 'HUAWEI nova 14 Ultra', + os: 'harmony', + screen: { width: 363, height: 817 }, + pixelRatio: 3.5, + system: 'HarmonyOS 5.0', + statusBarHeight: 48, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 48 }, + releaseYear: 2025, + }, + { + name: 'HUAWEI Pura X (inner)', + os: 'harmony', + screen: { width: 440, height: 707 }, + pixelRatio: 3, + system: 'HarmonyOS 5.0', + statusBarHeight: 39, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 39 }, + releaseYear: 2025, + }, + { + name: 'HUAWEI Pura X (outer)', + os: 'harmony', + screen: { width: 327, height: 327 }, + pixelRatio: 3, + system: 'HarmonyOS 5.0', + statusBarHeight: 0, + navigationBarHeight: 0, + navigationBarHeightLandscape: 0, + releaseYear: 2025, + }, + { + name: 'HUAWEI Mate X6 (inner)', + os: 'harmony', + screen: { width: 717, height: 781 }, + pixelRatio: 3.125, + system: 'HarmonyOS 5.0', + statusBarHeight: 39, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 39 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Mate X6 (outer)', + os: 'harmony', + screen: { width: 346, height: 781 }, + pixelRatio: 3.125, + system: 'HarmonyOS 5.0', + statusBarHeight: 39, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 39 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Pura 70', + os: 'harmony', + screen: { width: 372, height: 818 }, + pixelRatio: 3.375, + system: 'HarmonyOS 5.0', + statusBarHeight: 36, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 36 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Pura 70 Pro', + os: 'harmony', + screen: { width: 373, height: 843 }, + pixelRatio: 3.375, + system: 'HarmonyOS 5.0', + statusBarHeight: 38, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 38 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI Mate X5 (inner)', + os: 'harmony', + screen: { width: 712, height: 799 }, + pixelRatio: 3.125, + system: 'HarmonyOS 5.0', + statusBarHeight: 36, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 36 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI Mate X5 (outer)', + os: 'harmony', + screen: { width: 346, height: 801 }, + pixelRatio: 3.125, + system: 'HarmonyOS 5.0', + statusBarHeight: 36, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 36 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI Pocket 2', + os: 'harmony', + screen: { width: 364, height: 861 }, + pixelRatio: 3.125, + system: 'HarmonyOS 5.0', + statusBarHeight: 34, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 34 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI nova 13', + os: 'harmony', + screen: { width: 361, height: 804 }, + pixelRatio: 3, + system: 'HarmonyOS 5.0', + statusBarHeight: 35, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 35 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI nova 13 Pro', + os: 'harmony', + screen: { width: 363, height: 823 }, + pixelRatio: 3.375, + system: 'HarmonyOS 5.0', + statusBarHeight: 44, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 44 }, + releaseYear: 2024, + }, + { + name: 'HUAWEI nova 12', + os: 'harmony', + screen: { width: 361, height: 804 }, + pixelRatio: 3, + system: 'HarmonyOS 5.0', + statusBarHeight: 35, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 35 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI nova 12 Pro', + os: 'harmony', + screen: { width: 363, height: 823 }, + pixelRatio: 3.375, + system: 'HarmonyOS 5.0', + statusBarHeight: 44, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 44 }, + releaseYear: 2023, + }, + { + name: 'HUAWEI Pura X Max (outer)', + os: 'harmony', + screen: { width: 460, height: 672 }, + pixelRatio: 2.75, + system: 'HarmonyOS 5.0', + statusBarHeight: 66, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 66 }, + releaseYear: 2026, + }, // Table stores portrait; the unfolded (wider-than-tall) screen is what orientation=landscape reads out. - { name: 'HUAWEI Pura X Max (inner)', os: 'harmony', screen: { width: 665, height: 940 }, pixelRatio: 2.75, system: 'HarmonyOS 5.0', statusBarHeight: 72, navigationBarHeight: 28, navigationBarHeightLandscape: 28, safeAreaInsets: { top: 72 } }, + { + name: 'HUAWEI Pura X Max (inner)', + os: 'harmony', + screen: { width: 665, height: 940 }, + pixelRatio: 2.75, + system: 'HarmonyOS 5.0', + statusBarHeight: 72, + navigationBarHeight: 28, + navigationBarHeightLandscape: 28, + safeAreaInsets: { top: 72 }, + releaseYear: 2026, + }, ] diff --git a/packages/devices/src/presets/index.ts b/packages/devices/src/presets/index.ts index 8fe92e7..8d3962d 100644 --- a/packages/devices/src/presets/index.ts +++ b/packages/devices/src/presets/index.ts @@ -32,7 +32,7 @@ * device means adding a line; presets.test.ts holds the invariants that every * row has to satisfy. */ -import type { DeviceProfile } from '../devices.js' +import type { PresetDeviceProfile } from '../devices.js' import { ANDROID_DEVICES } from './android.js' import { HARMONY_DEVICES } from './harmony.js' import { IOS_DEVICES } from './ios.js' @@ -40,12 +40,12 @@ import { IOS_DEVICES } from './ios.js' export { ANDROID_DEVICES, HARMONY_DEVICES, IOS_DEVICES } /** Every device in the table, ordered iOS → Android → HarmonyOS. */ -export const DEVICES: readonly DeviceProfile[] = [...IOS_DEVICES, ...ANDROID_DEVICES, ...HARMONY_DEVICES] +export const DEVICES: readonly PresetDeviceProfile[] = [...IOS_DEVICES, ...ANDROID_DEVICES, ...HARMONY_DEVICES] const BY_NAME = new Map(DEVICES.map((device) => [device.name, device])) /** What renders when nothing asked for a particular device. */ -export const DEFAULT_DEVICE: DeviceProfile = BY_NAME.get('iPhone X')! +export const DEFAULT_DEVICE: PresetDeviceProfile = BY_NAME.get('iPhone X')! /** * Looks a device up by its `name`, which is unique across the whole table. @@ -54,7 +54,7 @@ export const DEFAULT_DEVICE: DeviceProfile = BY_NAME.get('iPhone X')! * caller can pass a missing attribute straight through * @returns the table's own object, or undefined when no row has that name */ -export function findDevice(name: string | null | undefined): DeviceProfile | undefined { +export function findDevice(name: string | null | undefined): PresetDeviceProfile | undefined { if (!name) return undefined return BY_NAME.get(name) } diff --git a/packages/devices/src/presets/ios.ts b/packages/devices/src/presets/ios.ts index 29c354e..4e231ea 100644 --- a/packages/devices/src/presets/ios.ts +++ b/packages/devices/src/presets/ios.ts @@ -4,73 +4,865 @@ * One line per device. Omitted fields fall back to the platform defaults in * devices.ts — see the README for what each source could and could not tell us. */ -import type { DeviceProfile } from '../devices.js' +import type { PresetDeviceProfile } from '../devices.js' -const LEGACY_IPHONE_SHELL = { screenRadius: 0, bodyRadius: 38, bezelInsets: { top: 44, bottom: 48 }, homeButton: { diameter: 30 } } +const LEGACY_IPHONE_SHELL = { + screenRadius: 0, + bodyRadius: 38, + bezelInsets: { top: 44, bottom: 48 }, + homeButton: { diameter: 30 }, +} /** iPhones and iPads. Part of DEVICES; listed here so a host can offer one platform alone. */ -export const IOS_DEVICES: readonly DeviceProfile[] = [ - { name: 'iPhone SE', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 15.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone XR', os: 'ios', screen: { width: 414, height: 896 }, pixelRatio: 2, system: 'iOS 18.5', statusBarHeight: 48, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 48, bottom: 34 }, safeAreaInsetsLandscape: { left: 48, right: 48, bottom: 21 }, cutout: { shape: 'notch', width: 231, height: 33, top: 0 } }, - { name: 'iPhone 12 Pro', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 210, height: 32, top: 0 } }, - { name: 'iPhone 14', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, shell: { screenRadius: 47 } }, - { name: 'iPhone 14 Plus', os: 'ios', screen: { width: 428, height: 926 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 161, height: 34, top: 0 } }, - { name: 'iPhone 14 Pro', os: 'ios', screen: { width: 393, height: 852 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, shell: { screenRadius: 55 } }, - { name: 'iPhone 14 Pro Max', os: 'ios', screen: { width: 430, height: 932 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 15', os: 'ios', screen: { width: 393, height: 852 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 15 Plus', os: 'ios', screen: { width: 430, height: 932 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 15 Pro', os: 'ios', screen: { width: 393, height: 852 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 15 Pro Max', os: 'ios', screen: { width: 430, height: 932 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 16e', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 } }, - { name: 'iPhone 16', os: 'ios', screen: { width: 393, height: 852 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 16 Plus', os: 'ios', screen: { width: 430, height: 932 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 59, bottom: 34 }, safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 11 } }, - { name: 'iPhone 16 Pro', os: 'ios', screen: { width: 402, height: 874 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, shell: { screenRadius: 62 } }, - { name: 'iPhone 16 Pro Max', os: 'ios', screen: { width: 440, height: 956 }, pixelRatio: 3, system: 'iOS 18.5', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 21 }, cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, shell: { screenRadius: 62 } }, - { name: 'iPad Mini', os: 'ios', formFactor: 'tablet', screen: { width: 744, height: 1133 }, pixelRatio: 2, system: 'iOS 18.5', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPad Pro 13', os: 'ios', formFactor: 'tablet', screen: { width: 1032, height: 1376 }, pixelRatio: 2, system: 'iPadOS 18.5', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPhone 5', os: 'ios', screen: { width: 320, height: 568 }, pixelRatio: 2, system: 'iOS 10.3', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 6/7/8', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 6/7/8 Plus', os: 'ios', screen: { width: 414, height: 736 }, pixelRatio: 3, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 44, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone X', os: 'ios', screen: { width: 375, height: 812 }, pixelRatio: 3, system: 'iOS 16.0', statusBarHeight: 44, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 44, bottom: 34 }, safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, cutout: { shape: 'notch', width: 209, height: 30, top: 0 }, shell: { screenRadius: 39 } }, - { name: 'iPhone XS Max', os: 'ios', screen: { width: 414, height: 896 }, pixelRatio: 3, system: 'iOS 11.0', statusBarHeight: 44, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 44, bottom: 34 }, safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, cutout: { shape: 'notch', width: 209, height: 30, top: 0 } }, - { name: 'iPhone 12/13 mini', os: 'ios', screen: { width: 375, height: 812 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 50, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 50, bottom: 34 }, safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, cutout: { shape: 'notch', width: 192, height: 30, top: 0 } }, - { name: 'iPhone 12/13 (Pro)', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 } }, - { name: 'iPhone 12/13 Pro Max', os: 'ios', screen: { width: 428, height: 926 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 161, height: 34, top: 0 } }, - { name: 'iPad', os: 'ios', formFactor: 'tablet', screen: { width: 768, height: 1024 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, statusBarHeightLandscape: 20, safeAreaInsets: { top: 20 }, safeAreaInsetsLandscape: { top: 20 }, shell: { screenRadius: 0 } }, - { name: 'iPad Pro 10.5-inch', os: 'ios', formFactor: 'tablet', screen: { width: 834, height: 1112 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, statusBarHeightLandscape: 20, safeAreaInsets: { top: 20 }, safeAreaInsetsLandscape: { top: 20 }, shell: { screenRadius: 0 } }, - { name: 'iPad Pro 12.9-inch', os: 'ios', formFactor: 'tablet', screen: { width: 1024, height: 1366 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPad (gen 5)', os: 'ios', formFactor: 'tablet', screen: { width: 768, height: 1024 }, pixelRatio: 2, system: 'iOS 12.2', statusBarHeight: 20, statusBarHeightLandscape: 20, safeAreaInsets: { top: 20 }, safeAreaInsetsLandscape: { top: 20 }, shell: { screenRadius: 0 } }, - { name: 'iPad (gen 6)', os: 'ios', formFactor: 'tablet', screen: { width: 768, height: 1024 }, pixelRatio: 2, system: 'iOS 12.2', statusBarHeight: 20, statusBarHeightLandscape: 20, safeAreaInsets: { top: 20 }, safeAreaInsetsLandscape: { top: 20 }, shell: { screenRadius: 0 } }, - { name: 'iPad (gen 7)', os: 'ios', formFactor: 'tablet', screen: { width: 810, height: 1080 }, pixelRatio: 2, system: 'iOS 12.2', statusBarHeight: 20, statusBarHeightLandscape: 20, safeAreaInsets: { top: 20 }, safeAreaInsetsLandscape: { top: 20 }, shell: { screenRadius: 0 } }, - { name: 'iPad (gen 11)', os: 'ios', formFactor: 'tablet', screen: { width: 820, height: 1180 }, pixelRatio: 2, system: 'iOS 18.5', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPad Pro 11', os: 'ios', formFactor: 'tablet', screen: { width: 834, height: 1194 }, pixelRatio: 2, system: 'iOS 12.2', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPhone 6', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 6 Plus', os: 'ios', screen: { width: 414, height: 736 }, pixelRatio: 3, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 44, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 7', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 7 Plus', os: 'ios', screen: { width: 414, height: 736 }, pixelRatio: 3, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 44, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 8', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 8 Plus', os: 'ios', screen: { width: 414, height: 736 }, pixelRatio: 3, system: 'iOS 11.0', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 44, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone SE (3rd gen)', os: 'ios', screen: { width: 375, height: 667 }, pixelRatio: 2, system: 'iOS 18.5', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 11', os: 'ios', screen: { width: 414, height: 896 }, pixelRatio: 2, system: 'iOS 12.2', statusBarHeight: 48, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 48, bottom: 34 }, safeAreaInsetsLandscape: { left: 48, right: 48, bottom: 21 }, cutout: { shape: 'notch', width: 231, height: 33, top: 0 } }, - { name: 'iPhone 11 Pro', os: 'ios', screen: { width: 375, height: 812 }, pixelRatio: 3, system: 'iOS 12.2', navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 44, bottom: 34 }, safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, cutout: { shape: 'notch', width: 209, height: 30, top: 0 } }, - { name: 'iPhone 11 Pro Max', os: 'ios', screen: { width: 414, height: 896 }, pixelRatio: 3, system: 'iOS 12.2', statusBarHeight: 44, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 44, bottom: 34 }, safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, cutout: { shape: 'notch', width: 209, height: 30, top: 0 } }, - { name: 'iPhone 12', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 14.4', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 210, height: 32, top: 0 } }, - { name: 'iPhone 12 Pro Max', os: 'ios', screen: { width: 428, height: 926 }, pixelRatio: 3, system: 'iOS 14.4', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 210, height: 32, top: 0 } }, - { name: 'iPhone 12 Mini', os: 'ios', screen: { width: 375, height: 812 }, pixelRatio: 3, system: 'iOS 14.4', statusBarHeight: 50, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 50, bottom: 34 }, safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, cutout: { shape: 'notch', width: 192, height: 30, top: 0 } }, - { name: 'iPhone 13', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 } }, - { name: 'iPhone 13 Pro', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 } }, - { name: 'iPhone 13 Pro Max', os: 'ios', screen: { width: 428, height: 926 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, cutout: { shape: 'notch', width: 161, height: 34, top: 0 } }, - { name: 'iPhone 13 Mini', os: 'ios', screen: { width: 375, height: 812 }, pixelRatio: 3, system: 'iOS 15.0', statusBarHeight: 50, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 50, bottom: 34 }, safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, cutout: { shape: 'notch', width: 192, height: 30, top: 0 } }, - { name: 'iPhone 17', os: 'ios', screen: { width: 402, height: 874 }, pixelRatio: 3, system: 'iOS 18.7', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 20 }, cutout: { shape: 'pill', width: 125, height: 37, top: 14 } }, - { name: 'iPhone Air', os: 'ios', screen: { width: 420, height: 912 }, pixelRatio: 3, system: 'iOS 18.7', statusBarHeight: 54, safeAreaInsets: { top: 68, bottom: 34 }, safeAreaInsetsLandscape: { left: 68, right: 68, bottom: 20 }, cutout: { shape: 'pill', width: 125, height: 37, top: 20 } }, - { name: 'iPhone 17 Pro', os: 'ios', screen: { width: 402, height: 874 }, pixelRatio: 3, system: 'iOS 26.0', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, safeAreaInsetsLandscape: { top: 0, left: 62, right: 62, bottom: 20 }, cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, shell: { screenRadius: 62 } }, - { name: 'iPhone 17 Pro Max', os: 'ios', screen: { width: 440, height: 956 }, pixelRatio: 3, system: 'iOS 18.7', statusBarHeight: 54, safeAreaInsets: { top: 62, bottom: 34 }, safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 20 }, cutout: { shape: 'pill', width: 125, height: 37, top: 14 } }, - { name: 'iPhone 17e', os: 'ios', screen: { width: 390, height: 844 }, pixelRatio: 3, system: 'iOS 18.7', statusBarHeight: 47, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 47, bottom: 34 }, safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 20 }, cutout: { shape: 'notch', width: 162, height: 34, top: 0 } }, - { name: 'iPhone 4', os: 'ios', screen: { width: 320, height: 480 }, pixelRatio: 2, system: 'iOS 7.1', statusBarHeight: 20, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPhone 5/SE', os: 'ios', screen: { width: 320, height: 568 }, pixelRatio: 2, system: 'iOS 10.3', statusBarHeight: 20, navigationBarHeight: 44, navigationBarHeightLandscape: 32, safeAreaInsets: { top: 20 }, shell: LEGACY_IPHONE_SHELL }, - { name: 'iPad Air', os: 'ios', formFactor: 'tablet', screen: { width: 820, height: 1180 }, pixelRatio: 2, system: 'iOS 13.3', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPad Pro', os: 'ios', formFactor: 'tablet', screen: { width: 1024, height: 1366 }, pixelRatio: 2, system: 'iOS 11.0', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPhone 17 Air', os: 'ios', screen: { width: 420, height: 912 }, pixelRatio: 3, system: 'iOS 18.0', statusBarHeight: 54, safeAreaInsets: { top: 68, bottom: 34 }, safeAreaInsetsLandscape: { left: 68, right: 68, bottom: 20 }, cutout: { shape: 'pill', width: 125, height: 37, top: 20 } }, - { name: 'iPad Pro M4', os: 'ios', formFactor: 'tablet', screen: { width: 834, height: 1210 }, pixelRatio: 2, system: 'iOS 17.5', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, - { name: 'iPad Air M2', os: 'ios', formFactor: 'tablet', screen: { width: 820, height: 1180 }, pixelRatio: 2, system: 'iOS 17.4', statusBarHeight: 24, statusBarHeightLandscape: 24, safeAreaInsets: { top: 24, bottom: 25 }, safeAreaInsetsLandscape: { top: 24, bottom: 25 }, shell: { screenRadius: 18 } }, +export const IOS_DEVICES: readonly PresetDeviceProfile[] = [ + { + name: 'iPhone SE', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 15.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2020, + }, + { + name: 'iPhone XR', + os: 'ios', + screen: { width: 414, height: 896 }, + pixelRatio: 2, + system: 'iOS 18.5', + statusBarHeight: 48, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 48, bottom: 34 }, + safeAreaInsetsLandscape: { left: 48, right: 48, bottom: 21 }, + cutout: { shape: 'notch', width: 231, height: 33, top: 0 }, + releaseYear: 2018, + }, + { + name: 'iPhone 12 Pro', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 210, height: 32, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 14', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + shell: { screenRadius: 47 }, + releaseYear: 2022, + }, + { + name: 'iPhone 14 Plus', + os: 'ios', + screen: { width: 428, height: 926 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 161, height: 34, top: 0 }, + releaseYear: 2022, + }, + { + name: 'iPhone 14 Pro', + os: 'ios', + screen: { width: 393, height: 852 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + shell: { screenRadius: 55 }, + releaseYear: 2022, + }, + { + name: 'iPhone 14 Pro Max', + os: 'ios', + screen: { width: 430, height: 932 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2022, + }, + { + name: 'iPhone 15', + os: 'ios', + screen: { width: 393, height: 852 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2023, + }, + { + name: 'iPhone 15 Plus', + os: 'ios', + screen: { width: 430, height: 932 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2023, + }, + { + name: 'iPhone 15 Pro', + os: 'ios', + screen: { width: 393, height: 852 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2023, + }, + { + name: 'iPhone 15 Pro Max', + os: 'ios', + screen: { width: 430, height: 932 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2023, + }, + { + name: 'iPhone 16e', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + releaseYear: 2025, + }, + { + name: 'iPhone 16', + os: 'ios', + screen: { width: 393, height: 852 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2024, + }, + { + name: 'iPhone 16 Plus', + os: 'ios', + screen: { width: 430, height: 932 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 59, bottom: 34 }, + safeAreaInsetsLandscape: { left: 59, right: 59, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 11 }, + releaseYear: 2024, + }, + { + name: 'iPhone 16 Pro', + os: 'ios', + screen: { width: 402, height: 874 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 62, bottom: 34 }, + safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, + shell: { screenRadius: 62 }, + releaseYear: 2024, + }, + { + name: 'iPhone 16 Pro Max', + os: 'ios', + screen: { width: 440, height: 956 }, + pixelRatio: 3, + system: 'iOS 18.5', + statusBarHeight: 54, + safeAreaInsets: { top: 62, bottom: 34 }, + safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 21 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, + shell: { screenRadius: 62 }, + releaseYear: 2024, + }, + { + name: 'iPad Mini', + os: 'ios', + formFactor: 'tablet', + screen: { width: 744, height: 1133 }, + pixelRatio: 2, + system: 'iOS 18.5', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2021, + }, + { + name: 'iPad Pro 13', + os: 'ios', + formFactor: 'tablet', + screen: { width: 1032, height: 1376 }, + pixelRatio: 2, + system: 'iPadOS 18.5', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2024, + }, + { + name: 'iPhone 5', + os: 'ios', + screen: { width: 320, height: 568 }, + pixelRatio: 2, + system: 'iOS 10.3', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2012, + }, + { + name: 'iPhone 6/7/8', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2014, + }, + { + name: 'iPhone 6/7/8 Plus', + os: 'ios', + screen: { width: 414, height: 736 }, + pixelRatio: 3, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 44, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2014, + }, + { + name: 'iPhone X', + os: 'ios', + screen: { width: 375, height: 812 }, + pixelRatio: 3, + system: 'iOS 16.0', + statusBarHeight: 44, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 44, bottom: 34 }, + safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, + cutout: { shape: 'notch', width: 209, height: 30, top: 0 }, + shell: { screenRadius: 39 }, + releaseYear: 2017, + }, + { + name: 'iPhone XS Max', + os: 'ios', + screen: { width: 414, height: 896 }, + pixelRatio: 3, + system: 'iOS 11.0', + statusBarHeight: 44, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 44, bottom: 34 }, + safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, + cutout: { shape: 'notch', width: 209, height: 30, top: 0 }, + releaseYear: 2018, + }, + { + name: 'iPhone 12/13 mini', + os: 'ios', + screen: { width: 375, height: 812 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 50, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 50, bottom: 34 }, + safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, + cutout: { shape: 'notch', width: 192, height: 30, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 12/13 (Pro)', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 12/13 Pro Max', + os: 'ios', + screen: { width: 428, height: 926 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 161, height: 34, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPad', + os: 'ios', + formFactor: 'tablet', + screen: { width: 768, height: 1024 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + statusBarHeightLandscape: 20, + safeAreaInsets: { top: 20 }, + safeAreaInsetsLandscape: { top: 20 }, + shell: { screenRadius: 0 }, + releaseYear: 2017, + }, + { + name: 'iPad Pro 10.5-inch', + os: 'ios', + formFactor: 'tablet', + screen: { width: 834, height: 1112 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + statusBarHeightLandscape: 20, + safeAreaInsets: { top: 20 }, + safeAreaInsetsLandscape: { top: 20 }, + shell: { screenRadius: 0 }, + releaseYear: 2017, + }, + { + name: 'iPad Pro 12.9-inch', + os: 'ios', + formFactor: 'tablet', + screen: { width: 1024, height: 1366 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2015, + }, + { + name: 'iPad (gen 5)', + os: 'ios', + formFactor: 'tablet', + screen: { width: 768, height: 1024 }, + pixelRatio: 2, + system: 'iOS 12.2', + statusBarHeight: 20, + statusBarHeightLandscape: 20, + safeAreaInsets: { top: 20 }, + safeAreaInsetsLandscape: { top: 20 }, + shell: { screenRadius: 0 }, + releaseYear: 2017, + }, + { + name: 'iPad (gen 6)', + os: 'ios', + formFactor: 'tablet', + screen: { width: 768, height: 1024 }, + pixelRatio: 2, + system: 'iOS 12.2', + statusBarHeight: 20, + statusBarHeightLandscape: 20, + safeAreaInsets: { top: 20 }, + safeAreaInsetsLandscape: { top: 20 }, + shell: { screenRadius: 0 }, + releaseYear: 2018, + }, + { + name: 'iPad (gen 7)', + os: 'ios', + formFactor: 'tablet', + screen: { width: 810, height: 1080 }, + pixelRatio: 2, + system: 'iOS 12.2', + statusBarHeight: 20, + statusBarHeightLandscape: 20, + safeAreaInsets: { top: 20 }, + safeAreaInsetsLandscape: { top: 20 }, + shell: { screenRadius: 0 }, + releaseYear: 2019, + }, + { + name: 'iPad (gen 11)', + os: 'ios', + formFactor: 'tablet', + screen: { width: 820, height: 1180 }, + pixelRatio: 2, + system: 'iOS 18.5', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2025, + }, + { + name: 'iPad Pro 11', + os: 'ios', + formFactor: 'tablet', + screen: { width: 834, height: 1194 }, + pixelRatio: 2, + system: 'iOS 12.2', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2018, + }, + { + name: 'iPhone 6', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2014, + }, + { + name: 'iPhone 6 Plus', + os: 'ios', + screen: { width: 414, height: 736 }, + pixelRatio: 3, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 44, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2014, + }, + { + name: 'iPhone 7', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2016, + }, + { + name: 'iPhone 7 Plus', + os: 'ios', + screen: { width: 414, height: 736 }, + pixelRatio: 3, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 44, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2016, + }, + { + name: 'iPhone 8', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2017, + }, + { + name: 'iPhone 8 Plus', + os: 'ios', + screen: { width: 414, height: 736 }, + pixelRatio: 3, + system: 'iOS 11.0', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 44, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2017, + }, + { + name: 'iPhone SE (3rd gen)', + os: 'ios', + screen: { width: 375, height: 667 }, + pixelRatio: 2, + system: 'iOS 18.5', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2022, + }, + { + name: 'iPhone 11', + os: 'ios', + screen: { width: 414, height: 896 }, + pixelRatio: 2, + system: 'iOS 12.2', + statusBarHeight: 48, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 48, bottom: 34 }, + safeAreaInsetsLandscape: { left: 48, right: 48, bottom: 21 }, + cutout: { shape: 'notch', width: 231, height: 33, top: 0 }, + releaseYear: 2019, + }, + { + name: 'iPhone 11 Pro', + os: 'ios', + screen: { width: 375, height: 812 }, + pixelRatio: 3, + system: 'iOS 12.2', + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 44, bottom: 34 }, + safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, + cutout: { shape: 'notch', width: 209, height: 30, top: 0 }, + releaseYear: 2019, + }, + { + name: 'iPhone 11 Pro Max', + os: 'ios', + screen: { width: 414, height: 896 }, + pixelRatio: 3, + system: 'iOS 12.2', + statusBarHeight: 44, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 44, bottom: 34 }, + safeAreaInsetsLandscape: { left: 44, right: 44, bottom: 21 }, + cutout: { shape: 'notch', width: 209, height: 30, top: 0 }, + releaseYear: 2019, + }, + { + name: 'iPhone 12', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 14.4', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 210, height: 32, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 12 Pro Max', + os: 'ios', + screen: { width: 428, height: 926 }, + pixelRatio: 3, + system: 'iOS 14.4', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 210, height: 32, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 12 Mini', + os: 'ios', + screen: { width: 375, height: 812 }, + pixelRatio: 3, + system: 'iOS 14.4', + statusBarHeight: 50, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 50, bottom: 34 }, + safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, + cutout: { shape: 'notch', width: 192, height: 30, top: 0 }, + releaseYear: 2020, + }, + { + name: 'iPhone 13', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + releaseYear: 2021, + }, + { + name: 'iPhone 13 Pro', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + releaseYear: 2021, + }, + { + name: 'iPhone 13 Pro Max', + os: 'ios', + screen: { width: 428, height: 926 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 21 }, + cutout: { shape: 'notch', width: 161, height: 34, top: 0 }, + releaseYear: 2021, + }, + { + name: 'iPhone 13 Mini', + os: 'ios', + screen: { width: 375, height: 812 }, + pixelRatio: 3, + system: 'iOS 15.0', + statusBarHeight: 50, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 50, bottom: 34 }, + safeAreaInsetsLandscape: { left: 50, right: 50, bottom: 21 }, + cutout: { shape: 'notch', width: 192, height: 30, top: 0 }, + releaseYear: 2021, + }, + { + name: 'iPhone 17', + os: 'ios', + screen: { width: 402, height: 874 }, + pixelRatio: 3, + system: 'iOS 18.7', + statusBarHeight: 54, + safeAreaInsets: { top: 62, bottom: 34 }, + safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 20 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, + releaseYear: 2025, + }, + { + name: 'iPhone Air', + os: 'ios', + screen: { width: 420, height: 912 }, + pixelRatio: 3, + system: 'iOS 18.7', + statusBarHeight: 54, + safeAreaInsets: { top: 68, bottom: 34 }, + safeAreaInsetsLandscape: { left: 68, right: 68, bottom: 20 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 20 }, + releaseYear: 2025, + }, + { + name: 'iPhone 17 Pro', + os: 'ios', + screen: { width: 402, height: 874 }, + pixelRatio: 3, + system: 'iOS 26.0', + statusBarHeight: 54, + safeAreaInsets: { top: 62, bottom: 34 }, + safeAreaInsetsLandscape: { top: 0, left: 62, right: 62, bottom: 20 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, + shell: { screenRadius: 62 }, + releaseYear: 2025, + }, + { + name: 'iPhone 17 Pro Max', + os: 'ios', + screen: { width: 440, height: 956 }, + pixelRatio: 3, + system: 'iOS 18.7', + statusBarHeight: 54, + safeAreaInsets: { top: 62, bottom: 34 }, + safeAreaInsetsLandscape: { left: 62, right: 62, bottom: 20 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 14 }, + releaseYear: 2025, + }, + { + name: 'iPhone 17e', + os: 'ios', + screen: { width: 390, height: 844 }, + pixelRatio: 3, + system: 'iOS 18.7', + statusBarHeight: 47, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 47, bottom: 34 }, + safeAreaInsetsLandscape: { left: 47, right: 47, bottom: 20 }, + cutout: { shape: 'notch', width: 162, height: 34, top: 0 }, + releaseYear: 2026, + }, + { + name: 'iPhone 4', + os: 'ios', + screen: { width: 320, height: 480 }, + pixelRatio: 2, + system: 'iOS 7.1', + statusBarHeight: 20, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2010, + }, + { + name: 'iPhone 5/SE', + os: 'ios', + screen: { width: 320, height: 568 }, + pixelRatio: 2, + system: 'iOS 10.3', + statusBarHeight: 20, + navigationBarHeight: 44, + navigationBarHeightLandscape: 32, + safeAreaInsets: { top: 20 }, + shell: LEGACY_IPHONE_SHELL, + releaseYear: 2012, + }, + { + name: 'iPad Air', + os: 'ios', + formFactor: 'tablet', + screen: { width: 820, height: 1180 }, + pixelRatio: 2, + system: 'iOS 13.3', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2020, + }, + { + name: 'iPad Pro', + os: 'ios', + formFactor: 'tablet', + screen: { width: 1024, height: 1366 }, + pixelRatio: 2, + system: 'iOS 11.0', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2018, + }, + { + name: 'iPhone 17 Air', + os: 'ios', + screen: { width: 420, height: 912 }, + pixelRatio: 3, + system: 'iOS 18.0', + statusBarHeight: 54, + safeAreaInsets: { top: 68, bottom: 34 }, + safeAreaInsetsLandscape: { left: 68, right: 68, bottom: 20 }, + cutout: { shape: 'pill', width: 125, height: 37, top: 20 }, + releaseYear: 2025, + }, + { + name: 'iPad Pro M4', + os: 'ios', + formFactor: 'tablet', + screen: { width: 834, height: 1210 }, + pixelRatio: 2, + system: 'iOS 17.5', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2024, + }, + { + name: 'iPad Air M2', + os: 'ios', + formFactor: 'tablet', + screen: { width: 820, height: 1180 }, + pixelRatio: 2, + system: 'iOS 17.4', + statusBarHeight: 24, + statusBarHeightLandscape: 24, + safeAreaInsets: { top: 24, bottom: 25 }, + safeAreaInsetsLandscape: { top: 24, bottom: 25 }, + shell: { screenRadius: 18 }, + releaseYear: 2024, + }, ] diff --git a/packages/devices/src/profile-validation.test.ts b/packages/devices/src/profile-validation.test.ts index b560a9c..148fa7b 100644 --- a/packages/devices/src/profile-validation.test.ts +++ b/packages/devices/src/profile-validation.test.ts @@ -118,6 +118,10 @@ describe('assertDeviceProfile only checks the optional numeric fields when they it.each(['name', 'system', 'userAgent'] as const)('a numeric %s is rejected', (field) => { expectRejects({ ...VALID, [field]: 42 }, undefined, `deviceProfile.${field}`) }) + + it.each([2024.5, 1999, 2101])('an invalid release year (%s) is rejected', (releaseYear) => { + expectRejects({ ...VALID, releaseYear }, undefined, 'deviceProfile.releaseYear') + }) }) describe('the label argument replaces the "deviceProfile" prefix in every message', () => { diff --git a/packages/devices/src/validate.ts b/packages/devices/src/validate.ts index 15cd11f..ea20bb8 100644 --- a/packages/devices/src/validate.ts +++ b/packages/devices/src/validate.ts @@ -86,6 +86,12 @@ export function assertDeviceProfile(value: unknown, label = 'deviceProfile'): as expectString(`${label}.name`, value.name) expectFiniteAtLeast(`${label}.pixelRatio`, value.pixelRatio, 0, true) + if (value.releaseYear !== undefined) { + const releaseYear = value.releaseYear + if (typeof releaseYear !== 'number' || !Number.isInteger(releaseYear) || releaseYear < 2000 || releaseYear > 2100) { + throw new TypeError(`${label}.releaseYear must be an integer between 2000 and 2100, got ${releaseYear}`) + } + } for (const field of NONNEGATIVE_HEIGHT_FIELDS) { if (value[field] !== undefined) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9f1cc0..ddb34e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ importers: eslint: specifier: ^10.2.1 version: 10.9.1(jiti@2.7.0) + oxfmt: + specifier: 0.67.0 + version: 0.67.0 + oxlint: + specifier: 1.82.0 + version: 1.82.0 typescript: specifier: 5.9.2 version: 5.9.2 @@ -334,6 +340,234 @@ packages: '@oxc-project/types@0.148.0': resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==} + '@oxfmt/binding-android-arm-eabi@0.67.0': + resolution: {integrity: sha512-2olh3ioEmc4gRzQm7jxyB1b/PFBoFvTq8KdgYySeNpysDtA6DEg2Mvya4/I6flhL7G0eOrE8RD7JCNCIMhE16Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.67.0': + resolution: {integrity: sha512-ulfw8EHN1MBq/MFFDXw2/M1VAFu5mRUcnuZ8Hqbv9viAnFzO9t1jKSAsDqKYYDGMlytF/uj6Z5z5n/tHupnKhw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.67.0': + resolution: {integrity: sha512-MfONZx/O2o9M5v2jDFol556G9+A+P9xCuJ4DZ+qhE+RnaCdoscy6Eu5nq1dbuNxhwdJyZ6kLI7fnG9mwEeOeGg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.67.0': + resolution: {integrity: sha512-CYnIx5LvFVJnyJcCqwH2jxMKjFjqo5678MPjdmNFoSGMhlOvZ/xRZqvhDcolKrXc8fezW3AKh+C4wyoFuWOSSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.67.0': + resolution: {integrity: sha512-7/iF1orvIS9mxhKUqnmtMgm+OrSQ5acPwuvdQrm6ECgqbwPmC+Pw9cdke3sNfVN6pT2hbJ58+jP8BCThl5HXOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.67.0': + resolution: {integrity: sha512-yy+OGys07IZOpOmYPZoObKyUQLkfxeQqeCypk+1jaZd8HGo77hzvU1Jg8X3+W75o+9lszOjBfg0nkGtlwYywXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.67.0': + resolution: {integrity: sha512-wPIeeigXgJpwNw3wydYRt3U9iN9Y/ejpOZuYL9IA7igxWs7LIQMOkhKxTumRvy6dIv0iXKk3RTw3Vmjg0i+2sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.67.0': + resolution: {integrity: sha512-0+XNxcdbkTfxdcD4qW6Ci9n+mBNJ8xTBumnxKvKBmRFOdx0Wf8/KiHjCJayooXmYkqRpRVd98Q5egvzx5BLSgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxfmt/binding-linux-arm64-musl@0.67.0': + resolution: {integrity: sha512-I75LKPJyNOYUzkqAiAMIE31+Ye7xtQXZdoty1IXn4B+bw5Zpmez5wfG19ejGpNnS/BzQ7LFS+7jxuTPb+vHiZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxfmt/binding-linux-ppc64-gnu@0.67.0': + resolution: {integrity: sha512-c2M5iRpe1QMZSRE/UvZoPdXBWb5Ic/ycvOyNiKCqPwQ/OyOKIMiJs02ynlNnjb7ZZJnRXYLmGcohoINOcwDK3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxfmt/binding-linux-riscv64-gnu@0.67.0': + resolution: {integrity: sha512-dQzzYlV24Udhfm5ECuSdgqRvFJU/CGHzcYYEO3dLM6W6+CHiBFrq9OjIllkdCcPhsoSQ8o223Dja84MOSzed9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxfmt/binding-linux-riscv64-musl@0.67.0': + resolution: {integrity: sha512-rFNq1CgX4qMJANOq42LkAs90JE80GpiaEohAV2qn/gT2hGjQTW1zBO5zQBxArI4926pM1OSzo3CN0tBszGBIaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxfmt/binding-linux-s390x-gnu@0.67.0': + resolution: {integrity: sha512-Sky6rEdz2o5IGq01lPhS12yEvDdChVEcaYrcLHkveh4Fx0qPjljE/Iul6SX/bRMl6lNc8J7J/mDQdzgBdA++Pg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxfmt/binding-linux-x64-gnu@0.67.0': + resolution: {integrity: sha512-vPXmlNORV8AZq2Ocxh07pxwMjfENUWCV/eZArnao0qC3NO/hDeTVkQvee7SJJUbIiF5PZbBa4kYmaXnu7Rk58w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxfmt/binding-linux-x64-musl@0.67.0': + resolution: {integrity: sha512-x/WAtFqYtVr3vZ9ni8nr4kn9whSitg8fOljq/pZzBpxopRdY1BMLZCZkrbIbaBcYkm46qGbqVea2FCWmtQ2P9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxfmt/binding-openharmony-arm64@0.67.0': + resolution: {integrity: sha512-eRw9Neh4/aA6i+q/R3WU1gGQINhVM0J4fXIm6t27caOamkr/37uAkp1IdBx4zlJH97hmXR63z/q9n5c5dN7MzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.67.0': + resolution: {integrity: sha512-YIMvb+sGNYN2uc6+QK2HLPeEKM2vl7QZ5onQzpAJRb6pnf0DwUFP5R8tdS9R0l8hdUil2gu4Uxd0Yxrop0iT4w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.67.0': + resolution: {integrity: sha512-LzmU9MyACPzwNDIK0ItMedHPz735Ug7ELWguxo4/kuy6zWuDoeglOAEFCY8jLg0PzRpFO3hDyLFe2Gu2eFDeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.67.0': + resolution: {integrity: sha512-sbQOIDNLUEeVZcAJcSL5VURn7kfjvilPviody4Yl5n8lQCDtUm+C9oHTTwZS/m4d/Z6Vv3jNEiAofH932NPPCg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.82.0': + resolution: {integrity: sha512-a3LB+C5Dsj5b/qtmG/mv5WrzuiXEpg1KF5nXWcEvaoN5TYAqkIvxPOwTPp3Jy/FoGpRo8zsTFhMElMXfeoOEzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.82.0': + resolution: {integrity: sha512-OBlhRgNqFblGpGenno/aqOfJLOkQ2B8Ig3iDAalfn0H8hJGZKXPeexCRTDm6uwv6YUjSA9Xnwt1y/Bgj5ZH8uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.82.0': + resolution: {integrity: sha512-dsopxqtY5ZdyT9uLHyGt1SyiLop6hi7hWI3PKpePodkRQOkLaCm+OE4fR9CAz9qdfjiFO8531tX/QDyP/psjFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.82.0': + resolution: {integrity: sha512-94Lu0SgTClKColU66g1VDuigV3HkcbkJBnTtZjGYfE8UPugaWDgKrm2icjC6HJVUYler2OXaHP/X0TBy8+CowQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.82.0': + resolution: {integrity: sha512-hne/V06ewhh1i0w8+l7GDNROAGCGPmyFuOwiP7YTRu0JycyStJ4785dmF8xU5p0uUwt2emvIF9vc7Xjis+cJ0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.82.0': + resolution: {integrity: sha512-aWY2xtbZf1LneW9Qsv/n2Sp8gOu74JrlQzEtj4coHX2SHFrCfhmAumaU+sI/A5nr+yoTRTSmI/pL2s6ADlNSkw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.82.0': + resolution: {integrity: sha512-Fe+TtXCXMh/5f7kWlZ2VAwsMumZWtraFlKVk1NJlL52/beGwfDE7ov+/8gVirHzWokzGu7X65hSPq0ucPDskWQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.82.0': + resolution: {integrity: sha512-6azCZ6OJudlvipNttXCCQcyeFfcJ/NvUZdSN1z8elo73kCHtyQC7WTiUcSjWYvJ1jaq9KDUyMAoAS/vNzhBomA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-arm64-musl@1.82.0': + resolution: {integrity: sha512-PLEaSD8IAIIlwW4dwOd9YaxuxeOpwiXL4J24rcnE4iNtyM5j9Q9/3+gti08oXpx0u2ygNjRDx9xjWWpQonuJEw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-ppc64-gnu@1.82.0': + resolution: {integrity: sha512-D94em/BwknNTn4vqxjHh5wb2oL566eFhArabqKIr0cNZMHOJuiraFp1A8tXpH05bbE5tqwEfLXTI0MWEGtn3Dw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxlint/binding-linux-riscv64-gnu@1.82.0': + resolution: {integrity: sha512-MOprxBaoYU2D4VgxXCl3ghydThWtx7Um1lL51kGYNeQ5Al7WzsH7/tqGdNtbLrIWnjq3bsm13+nz/gRIxjrOXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-riscv64-musl@1.82.0': + resolution: {integrity: sha512-5h55QsfJ/luDXZzC20k6SNOY1Az+dCP9WvntKtcUWh2JhckAdwApY2ZusaBTwLENnReXU+A2fJtSrYvZJNKNPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-s390x-gnu@1.82.0': + resolution: {integrity: sha512-IE8NJNLlHr0CaXyGJPGVn0eTkUyoj1I2UfA8x7I4PSOYKsQ/6btVC7Pywrj5onk0cMH25r6Z38SoN3AvE5Zuog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxlint/binding-linux-x64-gnu@1.82.0': + resolution: {integrity: sha512-XUUUxaBo9XKl+J1B9EmP1cTGQPddzeURvoGkfwh/94PGnbW+hBprDljneoI2M1jzC1bzrIV3ihc7iM9UXl8+tg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-linux-x64-musl@1.82.0': + resolution: {integrity: sha512-SWLSFulX9TDuH6yvbPYp4+VNn6jkkIvvI+KiujDM5rWBRHEfkesCC/pCneIIUr6ovkxZ5fRtpi2v5Cz5FrMJZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-openharmony-arm64@1.82.0': + resolution: {integrity: sha512-BQy35f6ZUdNr9a6c7B7orxQTcLjByGT2z3WAgmRovpRwmPYAaJ+NTplmMzhdjdJ4qSchfMNZy/Ukg+qRg6zseQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.82.0': + resolution: {integrity: sha512-V4QhSTg5gctZue8RJjsGi7NpQPThr/p1/HfmiMC5kfe1KFEup9SQRVub4A6kijQjdHfxj7bLL1KO3QO7/5bwMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.82.0': + resolution: {integrity: sha512-TUSCLaKB2yktpFAJ/r3HAUYsaV/3DT7JS4iNKyoh3a9YNwD0UG7Ezh4D8m23654vQcU6P/RQrCAjRPKe4peP/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.82.0': + resolution: {integrity: sha512-VTVoRIWJTb+wvUX8EYoPArfFH02whuR10goFXE/LHRRX33ajRrFgqbcONXZMiF4C5rnattfkm87HqYn8jb8hmQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/binding-android-arm-eabi@1.2.7': resolution: {integrity: sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1393,6 +1627,32 @@ packages: resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} + oxfmt@0.67.0: + resolution: {integrity: sha512-vV7sSiPsaO0mSxdoUdayipVDFPzW/UQ+hrezEHa20+Tx1dnMdZLSRHMT0PdS67FFbhd74M1n08asW21aLGeCrA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint@1.82.0: + resolution: {integrity: sha512-+iFM1BGw1ntYJt3QngbJmjbrGxPaKMUADOXOijpWGnYcBPq8YZnQftSS1C+pVcDYy9YxqDVJKQqQkTazTQMboQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1607,6 +1867,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.1.2: + resolution: {integrity: sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -2061,6 +2325,120 @@ snapshots: '@oxc-project/types@0.148.0': {} + '@oxfmt/binding-android-arm-eabi@0.67.0': + optional: true + + '@oxfmt/binding-android-arm64@0.67.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.67.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.67.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.67.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.67.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.67.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.67.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.67.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.67.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.67.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.67.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.67.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.67.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.67.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.67.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.67.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.67.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.67.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.82.0': + optional: true + + '@oxlint/binding-android-arm64@1.82.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.82.0': + optional: true + + '@oxlint/binding-darwin-x64@1.82.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.82.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.82.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.82.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.82.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.82.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.82.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.82.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.82.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.82.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.82.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.82.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.82.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.82.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.82.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.82.0': + optional: true + '@rolldown/binding-android-arm-eabi@1.2.7': optional: true @@ -3252,6 +3630,52 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxfmt@0.67.0: + dependencies: + tinypool: 2.1.2 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.67.0 + '@oxfmt/binding-android-arm64': 0.67.0 + '@oxfmt/binding-darwin-arm64': 0.67.0 + '@oxfmt/binding-darwin-x64': 0.67.0 + '@oxfmt/binding-freebsd-x64': 0.67.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.67.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.67.0 + '@oxfmt/binding-linux-arm64-gnu': 0.67.0 + '@oxfmt/binding-linux-arm64-musl': 0.67.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.67.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.67.0 + '@oxfmt/binding-linux-riscv64-musl': 0.67.0 + '@oxfmt/binding-linux-s390x-gnu': 0.67.0 + '@oxfmt/binding-linux-x64-gnu': 0.67.0 + '@oxfmt/binding-linux-x64-musl': 0.67.0 + '@oxfmt/binding-openharmony-arm64': 0.67.0 + '@oxfmt/binding-win32-arm64-msvc': 0.67.0 + '@oxfmt/binding-win32-ia32-msvc': 0.67.0 + '@oxfmt/binding-win32-x64-msvc': 0.67.0 + + oxlint@1.82.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.82.0 + '@oxlint/binding-android-arm64': 1.82.0 + '@oxlint/binding-darwin-arm64': 1.82.0 + '@oxlint/binding-darwin-x64': 1.82.0 + '@oxlint/binding-freebsd-x64': 1.82.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.82.0 + '@oxlint/binding-linux-arm-musleabihf': 1.82.0 + '@oxlint/binding-linux-arm64-gnu': 1.82.0 + '@oxlint/binding-linux-arm64-musl': 1.82.0 + '@oxlint/binding-linux-ppc64-gnu': 1.82.0 + '@oxlint/binding-linux-riscv64-gnu': 1.82.0 + '@oxlint/binding-linux-riscv64-musl': 1.82.0 + '@oxlint/binding-linux-s390x-gnu': 1.82.0 + '@oxlint/binding-linux-x64-gnu': 1.82.0 + '@oxlint/binding-linux-x64-musl': 1.82.0 + '@oxlint/binding-openharmony-arm64': 1.82.0 + '@oxlint/binding-win32-arm64-msvc': 1.82.0 + '@oxlint/binding-win32-ia32-msvc': 1.82.0 + '@oxlint/binding-win32-x64-msvc': 1.82.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3528,6 +3952,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinypool@2.1.2: {} + tinyrainbow@3.1.1: {} tldts-core@7.4.11: {}