{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "phone",
  "title": "Phone",
  "type": "registry:ui",
  "description": "Front device frames for iPhone 17 Pro and Galaxy S26 Ultra. Real chassis aspect ratios (71.9×150 / 78.1×163.6), Dynamic Island / punch-hole, side buttons, status bar, home indicator / gesture bar, and official finish colors. Slot in any screen content.",
  "categories": [
    "display",
    "media"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/phone/phone.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { styleMap } from 'lit/directives/style-map.js'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nexport type PhoneModel = 'iphone-17-pro' | 'galaxy-s26-ultra'\nexport type PhoneVariant = 'apple' | 'android' | PhoneModel\nexport type PhoneSize = 'sm' | 'md' | 'lg'\nexport type IPhone17ProColor = 'cosmic-orange' | 'deep-blue' | 'silver'\nexport type GalaxyS26UltraColor = 'titanium-black' | 'titanium-gray' | 'titanium-silver' | 'cobalt-violet'\nexport type PhoneColor = IPhone17ProColor | GalaxyS26UltraColor | 'black' | 'natural' | 'blue'\n\ntype Finish = {\n  name: string\n  base: string\n  highlight: string\n  shadow: string\n  button: string\n}\n\nconst SIZE_CLASS: Record<PhoneSize, string> = {\n  sm: 'w-[240px]',\n  md: 'w-[300px]',\n  lg: 'w-[340px]',\n}\n\nconst SIZE_SCALE: Record<PhoneSize, number> = { sm: 0.8, md: 1, lg: 1.133 }\n\nfunction resolveModel(model?: PhoneModel, variant?: PhoneVariant): PhoneModel {\n  const value = model ?? variant\n  return value === 'galaxy-s26-ultra' || value === 'android' ? 'galaxy-s26-ultra' : 'iphone-17-pro'\n}\n\nfunction getFinish(model: PhoneModel, color?: string): Finish {\n  if (model === 'iphone-17-pro') {\n    const selected: IPhone17ProColor =\n      color === 'silver'\n        ? 'silver'\n        : color === 'deep-blue' || color === 'blue' || color === 'black'\n          ? 'deep-blue'\n          : 'cosmic-orange'\n\n    return {\n      'cosmic-orange': {\n        name: 'Cosmic Orange',\n        base: '#d65f27',\n        highlight: '#f49a69',\n        button: '#bd4e1d',\n        shadow: '0 30px 70px -22px rgba(184, 72, 24, 0.62), 0 10px 24px -14px rgba(0, 0, 0, 0.5)',\n      },\n      'deep-blue': {\n        name: 'Deep Blue',\n        base: '#183867',\n        highlight: '#526f9d',\n        button: '#102d56',\n        shadow: '0 30px 70px -22px rgba(18, 45, 91, 0.7), 0 10px 24px -14px rgba(0, 0, 0, 0.55)',\n      },\n      silver: {\n        name: 'Silver',\n        base: '#b9babd',\n        highlight: '#f1f1f2',\n        button: '#9b9ca0',\n        shadow: '0 30px 70px -24px rgba(0, 0, 0, 0.42), 0 10px 24px -14px rgba(0, 0, 0, 0.38)',\n      },\n    }[selected]\n  }\n\n  const selected: GalaxyS26UltraColor =\n    color === 'titanium-gray'\n      ? 'titanium-gray'\n      : color === 'titanium-silver' || color === 'silver' || color === 'natural'\n        ? 'titanium-silver'\n        : color === 'cobalt-violet' || color === 'blue'\n          ? 'cobalt-violet'\n          : 'titanium-black'\n\n  return {\n    'titanium-black': {\n      name: 'Titanium Black',\n      base: '#242426',\n      highlight: '#66666a',\n      button: '#171719',\n      shadow: '0 30px 68px -22px rgba(0, 0, 0, 0.7), 0 10px 24px -14px rgba(0, 0, 0, 0.65)',\n    },\n    'titanium-gray': {\n      name: 'Titanium Gray',\n      base: '#66666a',\n      highlight: '#aaa9ad',\n      button: '#4a4a4e',\n      shadow: '0 30px 68px -24px rgba(0, 0, 0, 0.52), 0 10px 24px -14px rgba(0, 0, 0, 0.5)',\n    },\n    'titanium-silver': {\n      name: 'Titanium Silver',\n      base: '#b9babe',\n      highlight: '#eeeeef',\n      button: '#999a9e',\n      shadow: '0 30px 68px -24px rgba(0, 0, 0, 0.4), 0 10px 24px -14px rgba(0, 0, 0, 0.4)',\n    },\n    'cobalt-violet': {\n      name: 'Cobalt Violet',\n      base: '#4b3a74',\n      highlight: '#8a78b1',\n      button: '#352658',\n      shadow: '0 30px 68px -22px rgba(48, 31, 86, 0.68), 0 10px 24px -14px rgba(0, 0, 0, 0.55)',\n    },\n  }[selected]\n}\n\nfunction getPhoneVars(model: PhoneModel, scale: number, finish: Finish): Record<string, string> {\n  const px = (value: number) => `${value * scale}px`\n  const common = {\n    '--phone-finish': finish.base,\n    '--phone-finish-highlight': finish.highlight,\n    '--phone-button': finish.button,\n    '--phone-button-depth': px(3),\n    '--phone-button-radius': px(1.5),\n  }\n\n  if (model === 'iphone-17-pro') {\n    return {\n      ...common,\n      '--phone-rim': px(2.5),\n      '--phone-bezel': px(7.5),\n      '--phone-outer-radius': px(44),\n      '--phone-bezel-radius': px(41.5),\n      '--phone-screen-radius': px(34),\n      '--phone-island-width': px(88),\n      '--phone-island-height': px(27),\n      '--phone-island-top': px(11),\n      '--phone-status-height': px(44),\n      '--phone-status-padding': px(18),\n      '--phone-status-size': px(12),\n      '--phone-indicator-height': px(5),\n      '--phone-indicator-bottom': px(8),\n    }\n  }\n\n  return {\n    ...common,\n    '--phone-rim': px(2.2),\n    '--phone-bezel': px(5.8),\n    '--phone-outer-radius': px(26.9),\n    '--phone-bezel-radius': px(24.7),\n    '--phone-screen-radius': px(18.9),\n    '--phone-camera-size': px(12),\n    '--phone-camera-top': px(10),\n    '--phone-status-height': px(36),\n    '--phone-status-padding': px(16),\n    '--phone-status-size': px(12),\n    '--phone-indicator-height': px(4),\n    '--phone-indicator-bottom': px(8),\n  }\n}\n\nconst trueUnlessFalse = {\n  fromAttribute: (v: string | null) => v !== 'false',\n}\n\n/**\n * <uip-phone> — the registry Phone mockup as a web component.\n */\nexport class UipPhone extends LitElement {\n  static styles = [tailwind, css`:host { display: inline-block; vertical-align: middle; }`]\n\n  static properties = {\n    model: { reflect: true },\n    variant: { reflect: true },\n    size: { reflect: true },\n    color: { reflect: true },\n    showStatusBar: { converter: trueUnlessFalse, attribute: 'show-status-bar' },\n    time: {},\n    showHomeIndicator: { type: Boolean, attribute: 'show-home-indicator' },\n    showNavBar: { type: Boolean, attribute: 'show-nav-bar' },\n  }\n\n  model?: PhoneModel\n  variant?: PhoneVariant\n  size: PhoneSize = 'md'\n  color?: string\n  showStatusBar = true\n  time = '9:41'\n  showHomeIndicator?: boolean\n  showNavBar?: boolean\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'phone')\n  }\n\n  render() {\n    const resolvedModel = resolveModel(this.model, this.variant)\n    const isIPhone = resolvedModel === 'iphone-17-pro'\n    const finish = getFinish(resolvedModel, this.color)\n    const phoneVars = getPhoneVars(resolvedModel, SIZE_SCALE[this.size] ?? 1, finish)\n    const showHome = this.showHomeIndicator ?? isIPhone\n    const showNav = this.showNavBar ?? !isIPhone\n    const aspectRatio = isIPhone ? '71.9 / 150' : '78.1 / 163.6'\n\n    return html`\n      <div\n        part=\"base\"\n        data-uipkge=\"\"\n        data-slot=\"phone\"\n        data-model=${resolvedModel}\n        data-size=${this.size}\n        data-color=${finish.name.toLowerCase().replaceAll(' ', '-')}\n        role=\"group\"\n        aria-label=${`${isIPhone ? 'iPhone 17 Pro' : 'Galaxy S26 Ultra'} preview, ${finish.name}`}\n        class=${cn('relative inline-block max-w-full select-none', SIZE_CLASS[this.size] ?? SIZE_CLASS.md)}\n        style=${styleMap(phoneVars)}\n      >\n        <div\n          data-slot=\"phone-chassis\"\n          class=\"relative w-full\"\n          style=${styleMap({\n            aspectRatio,\n            borderRadius: 'var(--phone-outer-radius)',\n            background:\n              'linear-gradient(115deg, var(--phone-finish-highlight) 0%, var(--phone-finish) 38%, color-mix(in srgb, var(--phone-finish) 72%, black) 100%)',\n            boxShadow: finish.shadow,\n          })}\n        >\n          <div\n            aria-hidden=\"true\"\n            class=\"pointer-events-none absolute inset-0 z-30 border border-white/25 shadow-[inset_0_0_1px_1px_rgba(255,255,255,0.18),inset_0_0_3px_rgba(0,0,0,0.5)]\"\n            style=${styleMap({ borderRadius: 'var(--phone-outer-radius)' })}\n          ></div>\n\n          ${isIPhone\n            ? html`\n                <div\n                  data-slot=\"phone-action-button\"\n                  data-side=\"left\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[15.8%] left-0 h-[4.8%] -translate-x-full bg-[var(--phone-button)] shadow-[inset_1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: 'var(--phone-button-radius) 0 0 var(--phone-button-radius)',\n                  })}\n                ></div>\n                <div\n                  data-slot=\"phone-volume-button\"\n                  data-control=\"up\"\n                  data-side=\"left\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[23.3%] left-0 h-[7.2%] -translate-x-full bg-[var(--phone-button)] shadow-[inset_1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: 'var(--phone-button-radius) 0 0 var(--phone-button-radius)',\n                  })}\n                ></div>\n                <div\n                  data-slot=\"phone-volume-button\"\n                  data-control=\"down\"\n                  data-side=\"left\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[32.2%] left-0 h-[7.2%] -translate-x-full bg-[var(--phone-button)] shadow-[inset_1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: 'var(--phone-button-radius) 0 0 var(--phone-button-radius)',\n                  })}\n                ></div>\n                <div\n                  data-slot=\"phone-side-button\"\n                  data-side=\"right\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[24.1%] right-0 h-[12.2%] translate-x-full bg-[var(--phone-button)] shadow-[inset_-1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: '0 var(--phone-button-radius) var(--phone-button-radius) 0',\n                  })}\n                ></div>\n                <div\n                  data-slot=\"phone-camera-control\"\n                  data-side=\"right\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[68.5%] right-0 h-[8.7%] translate-x-full bg-[var(--phone-button)] shadow-[inset_-1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: '0 var(--phone-button-radius) var(--phone-button-radius) 0',\n                  })}\n                ></div>\n              `\n            : html`\n                <div\n                  data-slot=\"phone-volume-button\"\n                  data-side=\"right\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[17.2%] right-0 h-[13.8%] translate-x-full bg-[var(--phone-button)] shadow-[inset_-1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: '0 var(--phone-button-radius) var(--phone-button-radius) 0',\n                  })}\n                ></div>\n                <div\n                  data-slot=\"phone-side-button\"\n                  data-side=\"right\"\n                  aria-hidden=\"true\"\n                  class=\"absolute top-[33.1%] right-0 h-[8.9%] translate-x-full bg-[var(--phone-button)] shadow-[inset_-1px_0_rgba(255,255,255,0.22)]\"\n                  style=${styleMap({\n                    width: 'var(--phone-button-depth)',\n                    borderRadius: '0 var(--phone-button-radius) var(--phone-button-radius) 0',\n                  })}\n                ></div>\n              `}\n\n          <div\n            data-slot=\"phone-bezel\"\n            aria-hidden=\"true\"\n            class=\"absolute bg-[#050506] shadow-[inset_0_0_1px_rgba(255,255,255,0.32)]\"\n            style=${styleMap({ inset: 'var(--phone-rim)', borderRadius: 'var(--phone-bezel-radius)' })}\n          ></div>\n\n          <div\n            data-slot=\"phone-screen\"\n            class=\"bg-background text-foreground absolute overflow-hidden\"\n            style=${styleMap({\n              inset: 'calc(var(--phone-rim) + var(--phone-bezel))',\n              borderRadius: 'var(--phone-screen-radius)',\n            })}\n          >\n            <div data-slot=\"phone-content\" class=\"absolute inset-0 z-10 overflow-auto\">\n              <slot></slot>\n            </div>\n\n            ${isIPhone\n              ? html`\n                  <div\n                    data-slot=\"phone-island\"\n                    aria-hidden=\"true\"\n                    class=\"absolute left-1/2 z-40 -translate-x-1/2 rounded-full bg-[#050506] shadow-[0_1px_2px_rgba(0,0,0,0.55)]\"\n                    style=${styleMap({\n                      top: 'var(--phone-island-top)',\n                      width: 'var(--phone-island-width)',\n                      height: 'var(--phone-island-height)',\n                    })}\n                  >\n                    <span class=\"absolute top-1/2 right-[9%] aspect-square h-[36%] -translate-y-1/2 rounded-full bg-[#101929] ring-1 ring-[#172c4a]\">\n                      <span class=\"absolute top-[16%] left-[18%] size-[28%] rounded-full bg-white/25\"></span>\n                    </span>\n                  </div>\n                `\n              : html`\n                  <div\n                    data-slot=\"phone-camera\"\n                    aria-hidden=\"true\"\n                    class=\"absolute left-1/2 z-40 -translate-x-1/2 rounded-full bg-[#07090c] shadow-[0_0_0_1px_rgba(255,255,255,0.12)]\"\n                    style=${styleMap({\n                      top: 'var(--phone-camera-top)',\n                      width: 'var(--phone-camera-size)',\n                      height: 'var(--phone-camera-size)',\n                    })}\n                  >\n                    <span class=\"absolute inset-[24%] rounded-full bg-[#11243d]\"></span>\n                  </div>\n                `}\n\n            ${this.showStatusBar\n              ? html`\n                  <div\n                    data-slot=\"phone-status-bar\"\n                    aria-hidden=\"true\"\n                    class=\"pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center justify-between font-semibold text-current\"\n                    style=${styleMap({\n                      height: 'var(--phone-status-height)',\n                      paddingInline: 'var(--phone-status-padding)',\n                      fontSize: 'var(--phone-status-size)',\n                    })}\n                  >\n                    <span class=\"leading-none tabular-nums\">${this.time}</span>\n                    <div class=\"flex items-center gap-[0.28em]\">\n                      <svg viewBox=\"0 0 16 12\" class=\"h-[0.78em] w-[1.05em]\" fill=\"currentColor\" aria-hidden=\"true\">\n                        <path d=\"M1 11h2V8H1v3Zm4 0h2V6H5v5Zm4 0h2V3H9v8Zm4 0h2V0h-2v11Z\" />\n                      </svg>\n                      <svg viewBox=\"0 0 16 12\" class=\"h-[0.78em] w-[1.05em]\" fill=\"currentColor\" aria-hidden=\"true\">\n                        <path d=\"M8 10.5a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4ZM3.8 7.3l1.3 1.3a4.1 4.1 0 0 1 5.8 0l1.3-1.3a6 6 0 0 0-8.4 0ZM1 4.5l1.3 1.3a8 8 0 0 1 11.4 0L15 4.5a9.9 9.9 0 0 0-14 0Z\" />\n                      </svg>\n                      <span class=\"relative h-[0.82em] w-[1.65em] rounded-[0.2em] border-[0.12em] border-current\">\n                        <span class=\"absolute inset-[0.13em] right-[28%] rounded-[0.08em] bg-current\"></span>\n                        <span class=\"absolute top-[28%] -right-[0.22em] h-[44%] w-[0.12em] rounded-r-full bg-current/65\"></span>\n                      </span>\n                    </div>\n                  </div>\n                `\n              : nothing}\n\n            ${(isIPhone && showHome) || (!isIPhone && showNav)\n              ? html`\n                  <div\n                    data-slot=${isIPhone ? 'phone-home-indicator' : 'phone-nav-bar'}\n                    aria-hidden=\"true\"\n                    class=\"pointer-events-none absolute inset-x-0 z-40 flex justify-center\"\n                    style=${styleMap({ bottom: 'var(--phone-indicator-bottom)' })}\n                  >\n                    <div\n                      class=${cn(\n                        'rounded-full bg-current',\n                        isIPhone ? 'w-[36%] max-w-[120px]' : 'w-[27%] max-w-[100px] opacity-55',\n                      )}\n                      style=${styleMap({ height: 'var(--phone-indicator-height)' })}\n                    ></div>\n                  </div>\n                `\n              : nothing}\n\n            <div\n              aria-hidden=\"true\"\n              class=\"pointer-events-none absolute inset-0 z-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.5)]\"\n              style=${styleMap({ borderRadius: 'var(--phone-screen-radius)' })}\n            ></div>\n          </div>\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-phone') || customElements.define('uip-phone', UipPhone)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-phone': UipPhone\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/phone/phone.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
