import { LEGACY_BOTTOM_BEZEL, LEGACY_FRAME_RADIUS, LEGACY_HOME_BUTTON_RING, LEGACY_HOME_BUTTON_SIZE, LEGACY_SIDE_BEZEL, LEGACY_TOP_BEZEL, SIDE_BUTTON_RESERVE, } from 'sootsim-engine/ios/chrome-metrics' import { housingLayerHtml, resolveHousingArt, type HousingLayer, } from 'sootsim-engine/ios/duo-housing' import { cornerRadii, formatCornerRadii, getDevice, resolveDeviceFrameGeometry, type CornerRadii, type DeviceModel, } from 'sootsim-engine/settings' import { launchReapedChrome } from '../../../../scripts/lib/reap-browser' const METALLIC_RING_SHADOW = 'inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121' const FRAME_OUTLINE_BLEED = 1 type FrameButtonLayout = { side: 'left' | 'right' top: number height: number width: number } type LegacyHomeButtonLayout = { top: number left: number size: number ring: number } export interface FramedScreenshotLayout { model: DeviceModel renderScale: number outerWidth: number outerHeight: number logicalOuterWidth: number logicalOuterHeight: number logicalFrameWidth: number logicalFrameHeight: number logicalFrameLeft: number logicalFrameTop: number logicalScreenWidth: number logicalScreenHeight: number logicalScreenLeft: number logicalScreenTop: number logicalScreenRadius: CornerRadii logicalFrameRadius: CornerRadii // folding body art, painted before the screen; null draws one black body housingLayers: HousingLayer[] | null frameBackground: string frameOutline: string metallicRingShadow: string | null buttons: FrameButtonLayout[] legacyHomeButton: LegacyHomeButtonLayout | null showHomeIndicator: boolean logicalHomeIndicatorStripHeight: number } // Exact shell device frame layout for still exports. // Mirrors DeviceFrame's bezel/button geometry while intentionally excluding: // - the electron simulator top bar // - the browser rail / gutter // The screen bitmap itself still comes from the raw bridge screenshot. export function getFramedScreenshotLayout(model: DeviceModel): FramedScreenshotLayout { const spec = getDevice(model) const renderScale = spec.scale // legacy = home-button iPhone (SE). the canvas workspace also has no island / // home indicator / corner radius, but no status bar, so gate on statusBar > 0 // to keep it out of the SE shell (mirrors DeviceFrame.isLegacyHardware). const isLegacyHardware = spec.statusBarHeight > 0 && !spec.dynamicIsland && spec.homeIndicatorHeight === 0 && // home-button iphones have a square display, so any corner reads 0 spec.cornerRadius.topLeft === 0 const logicalScreenWidth = spec.width const logicalScreenHeight = spec.height const modernGeometry = resolveDeviceFrameGeometry(spec) const housingLayers = isLegacyHardware ? null : resolveHousingArt(spec.posture ?? 'closed', modernGeometry) const logicalFrameWidth = isLegacyHardware ? spec.width + LEGACY_SIDE_BEZEL * 2 : modernGeometry.frameWidth const logicalFrameHeight = isLegacyHardware ? spec.height + LEGACY_TOP_BEZEL + LEGACY_BOTTOM_BEZEL : modernGeometry.frameHeight const logicalFrameLeft = SIDE_BUTTON_RESERVE const logicalFrameTop = FRAME_OUTLINE_BLEED const logicalOuterWidth = logicalFrameWidth + logicalFrameLeft * 2 const logicalOuterHeight = logicalFrameHeight + FRAME_OUTLINE_BLEED * 2 const logicalScreenLeft = logicalFrameLeft + (isLegacyHardware ? LEGACY_SIDE_BEZEL : modernGeometry.screenLeft) const logicalScreenTop = isLegacyHardware ? LEGACY_TOP_BEZEL : modernGeometry.screenTop const logicalFrameRadius = isLegacyHardware ? cornerRadii(LEGACY_FRAME_RADIUS) : modernGeometry.frameRadius const buttons: FrameButtonLayout[] = [ { side: spec.hardwareButtons.lock.side, top: spec.hardwareButtons.lock.top, height: spec.hardwareButtons.lock.height, width: spec.hardwareButtons.width, }, { side: spec.hardwareButtons.ringToggle.side, top: spec.hardwareButtons.ringToggle.top, height: spec.hardwareButtons.ringToggle.height, width: spec.hardwareButtons.width, }, { side: spec.hardwareButtons.volumeUp.side, top: spec.hardwareButtons.volumeUp.top, height: spec.hardwareButtons.volumeUp.height, width: spec.hardwareButtons.width, }, { side: spec.hardwareButtons.volumeDown.side, top: spec.hardwareButtons.volumeDown.top, height: spec.hardwareButtons.volumeDown.height, width: spec.hardwareButtons.width, }, ] return { model, renderScale, outerWidth: Math.round(logicalOuterWidth * renderScale), outerHeight: Math.round(logicalOuterHeight * renderScale), logicalOuterWidth, logicalOuterHeight, logicalFrameWidth, logicalFrameHeight, logicalFrameLeft, logicalFrameTop, logicalScreenWidth, logicalScreenHeight, logicalScreenLeft, logicalScreenTop, logicalScreenRadius: isLegacyHardware ? cornerRadii(0) : modernGeometry.screenRadius, logicalFrameRadius, housingLayers, // the housing paints its own body layer, shadow included: its frame box is // wider than the body, because the hinge barrel protrudes past the spine. frameBackground: isLegacyHardware ? 'linear-gradient(180deg, #1b1c20 0%, #0f1012 42%, #050608 100%)' : housingLayers ? 'transparent' : '#000000', frameOutline: isLegacyHardware ? '0 0 0 1px #1f2125' : housingLayers ? 'none' : '0 0 0 1px #333', metallicRingShadow: isLegacyHardware || housingLayers ? null : METALLIC_RING_SHADOW, buttons, legacyHomeButton: isLegacyHardware ? { top: LEGACY_TOP_BEZEL + spec.height + (LEGACY_BOTTOM_BEZEL - LEGACY_HOME_BUTTON_SIZE) / 2, left: spec.width / 2 - LEGACY_HOME_BUTTON_SIZE / 2 + LEGACY_SIDE_BEZEL, size: LEGACY_HOME_BUTTON_SIZE, ring: LEGACY_HOME_BUTTON_RING, } : null, showHomeIndicator: spec.homeIndicatorHeight > 0, logicalHomeIndicatorStripHeight: spec.homeIndicatorHeight, } } function renderSideButtonHtml(button: FrameButtonLayout): string { const isRight = button.side === 'right' const clip = isRight ? 'inset(-3px -3px -3px 0)' : 'inset(-3px 0 -3px -3px)' return `
` } function renderFrameHtml( layout: FramedScreenshotLayout, rawImageDataUrl: string, ): string { const buttonsHtml = layout.buttons.map(renderSideButtonHtml).join('') const ringHtml = layout.metallicRingShadow ? ` ` : '' // the folding body's rail, glass ring, corner art, and hinge barrel. no // z-index, so the screen div after it paints on top. const housingHtml = (layout.housingLayers ?? []).map(housingLayerHtml).join('') const legacyHomeButtonHtml = layout.legacyHomeButton ? ` ` : '' const homeIndicatorHtml = layout.showHomeIndicator ? ` ` : '' return `