Phone Mockup — Free CSS iPhone Device Frame Snippet

Phone Mockup · Layouts · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Pure-CSS frame
No phone image — crisp at any size and theme-able.
Nested-radius bezel
Inner and outer radii create a realistic rim.
Dynamic island
A single positioned pill floats over the screen.
Hardware buttons
Volume and power slivers on the frame edges.
Live, scrollable screen
Real DOM content, not a static picture.
CSS status bar
SVG signal, wifi, and a battery with a fill.
Dark-mode toggle
Swap the screen theme with one class.
No dependency
Pure HTML/CSS/JS for mockups and showcases.

About this UI Snippet

Phone Mockup — Pure-CSS Device Frame for App Screens

Screenshot of the Phone Mockup snippet rendered live

A phone mockup is a device frame you drop a screen design into — for landing pages, app-store shots, portfolio pieces, and design presentations. This one is built entirely in HTML and CSS (no image of a phone), so it's crisp at any size, theme-able, and you can put live, scrolling, interactive content inside it. It ships with a sample finance app screen and a live status-bar clock, in vanilla JavaScript with no dependency.

The frame from nested rounded rectangles

The body is a dark rounded box with heavy padding, and the screen is an inner rounded box — the gap between their border-radius values creates the realistic bezel curve where a smaller radius nested inside a larger one reads as a uniform-width rim. An inset box-shadow adds a subtle metal edge, and a large soft drop shadow lifts the whole device off the page. No device photo is involved, so it scales losslessly.

Dynamic island and hardware buttons

The notch is a single absolutely-positioned pill (.pm-island) centered at the top with a high z-index so it floats over the screen content. The volume and power buttons are thin absolutely-positioned slivers hanging off the frame's left and right edges — tiny details that sell the realism for the cost of three empty spans.

A real, scrollable screen — not a picture

Because the screen is live DOM, the app inside it actually works: overflow-y:auto makes the transaction list scroll within the device, the status bar shows a CSS-drawn battery and signal bars, and a toggle switches the screen to dark mode by swapping one class. This is the advantage of a CSS mockup over an image — you can demo genuine interaction, dark mode, and responsive content in context.

The CSS-drawn status bar

The signal bars are four <rect>s of increasing height in a tiny inline SVG, the wifi glyph is a stroked arc, and the battery is a bordered box with a nub pseudo-element (::after) and a percentage fill — all currentColor, so they invert automatically in dark mode. A live clock updates the time so it never looks frozen.

Reusing it as a wrapper

Replace the .pm-app contents with your own screen and the frame becomes a reusable container for any mobile UI. Keep the device markup as a component and pass children into the screen slot — ideal for showcasing a mobile login screen or onboarding flow on a marketing page.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA CSS phone renders with a sample app screen inside it.
  2. 2
    Scroll the screenThe transaction list scrolls inside the device frame.
  3. 3
    Watch the clockThe status-bar time updates live like a real phone.
  4. 4
    Toggle dark modeOne class swaps the screen between light and dark.
  5. 5
    Drop in your screenReplace the app contents with your own mobile UI.
  6. 6
    Reuse as a wrapperKeep the frame as a component with a screen slot.

Real-world uses

Common Use Cases

App landing pages
Showcase a screen beside a product hero.
Portfolio and case studies
Frame work next to a bento grid gallery.
Design presentations
Present a mobile login screen in context.
Feature showcases
Pair with a tablet mockup for responsive demos.
Marketing screenshots
Wrap a music player for store-style shots.
Learning CSS device art
A reference for nested-radius bezels and CSS hardware.

Got questions?

Frequently Asked Questions

Pure CSS. The device is a dark rounded box with padding, the screen is a nested rounded box, and the gap between their border-radius values forms the bezel. The notch and side buttons are positioned spans. Because there's no photo, it stays sharp at any zoom and you can recolor or resize it freely.

Yes — that's the main advantage over a screenshot. The screen is live DOM, so content scrolls with overflow-y:auto, the status-bar clock ticks, and a class toggle switches the screen to dark mode. You can embed forms, lists, animations, or any other snippet and it behaves normally within the frame.

By nesting two rounded rectangles. The outer frame has a large border-radius and padding; the inner screen has a slightly smaller radius. The constant-width gap between them, plus an inset box-shadow for a metal edge, reads as a uniform bezel — a standard trick for CSS device frames that avoids any images.

Replace everything inside the .pm-app element with your markup. The frame, status bar, and scrolling are independent of the content, so your screen just needs to fit the screen width. Keeping the device portion as a reusable component lets you pass different screens in as children.

Make the device frame a component that renders a screen slot (children, a slot, or ng-content) so you can wrap any screen. Move the live-clock interval into a mount effect with cleanup. Toggle dark mode via a state-bound class. In Tailwind, build the frame with rounded-[46px], padding, and shadow utilities, and the status-bar icons with small inline SVGs.

Phone Mockup — Export as HTML, React, Vue, Angular & Tailwind

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Phone Mockup</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
    .pm-stage{text-align:center}
    
    .pm-phone{position:relative;width:270px;height:560px;background:#0b1220;border-radius:46px;padding:13px;box-shadow:0 30px 60px -20px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b}
    .pm-island{position:absolute;top:24px;left:50%;transform:translateX(-50%);width:88px;height:26px;background:#000;border-radius:14px;z-index:5}
    .pm-btn{position:absolute;background:#0b1220;border-radius:3px}
    .pm-vol-up{left:-3px;top:130px;width:3px;height:34px}
    .pm-vol-dn{left:-3px;top:176px;width:3px;height:34px}
    .pm-power{right:-3px;top:150px;width:3px;height:54px}
    
    .pm-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;background:#f1f5f9;color:#0f172a;transition:background .3s,color .3s}
    .pm-status{display:flex;justify-content:space-between;align-items:center;padding:14px 22px 4px;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
    .pm-status-r{display:flex;align-items:center;gap:5px;fill:currentColor}
    .pm-batt{width:22px;height:11px;border:1.4px solid currentColor;border-radius:3px;position:relative;display:inline-block;opacity:.9}
    .pm-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0}
    .pm-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px}
    
    .pm-app{padding:8px 18px 18px;height:calc(100% - 36px);overflow-y:auto}
    .pm-head{display:flex;align-items:center;justify-content:space-between;margin:8px 0 16px}
    .pm-head small{font-size:12px;color:#94a3b8;font-weight:600}
    .pm-head h2{font-size:20px;font-weight:800}
    .pm-ava{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800}
    
    .pm-balance{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;border-radius:18px;padding:18px;text-align:left;margin-bottom:14px}
    .pm-balance small{opacity:.8;font-size:12px}
    .pm-balance strong{display:block;font-size:27px;font-weight:800;margin:3px 0}
    .pm-up{font-size:12px;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:99px}
    
    .pm-actions{display:flex;gap:8px;margin-bottom:18px}
    .pm-actions button{flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)}
    
    .pm-list{display:flex;flex-direction:column;gap:13px}
    .pm-tx{display:flex;align-items:center;gap:11px}
    .pm-ic{width:38px;height:38px;border-radius:11px;color:#fff;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .pm-tx div{flex:1;text-align:left;min-width:0}
    .pm-tx b{font-size:13.5px;font-weight:700}
    .pm-tx small{display:block;font-size:11px;color:#94a3b8}
    .pm-tx em{font-style:normal;font-weight:800;font-size:13px}
    .pm-tx em.pos{color:#16a34a}
    
    .pm-screen.dark{background:#0f172a;color:#e2e8f0}
    .pm-screen.dark .pm-actions button{background:#1e293b;color:#a5b4fc}
    .pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small{color:#64748b}
    
    .pm-toggle{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
    .pm-toggle:hover{background:#1e293b}
  </style>
</head>
<body>
  <div class="pm-stage">
    <div class="pm-phone" id="pmPhone">
      <span class="pm-island"></span>
      <span class="pm-btn pm-vol-up"></span>
      <span class="pm-btn pm-vol-dn"></span>
      <span class="pm-btn pm-power"></span>
      <div class="pm-screen">
        <div class="pm-status">
          <span id="pmTime">9:41</span>
          <span class="pm-status-r">
            <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
            <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" stroke-width="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
            <span class="pm-batt"><i></i></span>
          </span>
        </div>
        <div class="pm-app">
          <header class="pm-head">
            <div><small>Good morning</small><h2>Alex</h2></div>
            <div class="pm-ava">A</div>
          </header>
          <div class="pm-balance">
            <small>Total balance</small>
            <strong>$12,480.55</strong>
            <span class="pm-up">+2.4% this week</span>
          </div>
          <div class="pm-actions">
            <button>Send</button><button>Request</button><button>Top up</button>
          </div>
          <div class="pm-list">
            <div class="pm-tx"><span class="pm-ic" style="background:#6366f1">N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#22c55e">S</span><div><b>Salary</b><small>Acme Inc</small></div><em class="pos">+$4,200</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#f59e0b">U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#ec4899">D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
          </div>
        </div>
      </div>
    </div>
    <button type="button" class="pm-toggle" id="pmToggle">Toggle dark screen</button>
  </div>
  <script>
    var screen = document.querySelector('.pm-screen');
    var timeEl = document.getElementById('pmTime');
    
    // Live status-bar clock so the mockup feels real.
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
    }
    tick();
    setInterval(tick, 10000);
    
    document.getElementById('pmToggle').addEventListener('click', function () {
      screen.classList.toggle('dark');
    });
  </script>
</body>
</html>
Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Phone Mockup</title>
  <!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    * {
      box-sizing:border-box;margin:0;padding:0
    }

    body {
      font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px
    }

    .pm-batt::after {
      content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0
    }

    .pm-batt i {
      position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px
    }

    .pm-head small {
      font-size:12px;color:#94a3b8;font-weight:600
    }

    .pm-head h2 {
      font-size:20px;font-weight:800
    }

    .pm-balance small {
      opacity:.8;font-size:12px
    }

    .pm-balance strong {
      display:block;font-size:27px;font-weight:800;margin:3px 0
    }

    .pm-actions button {
      flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)
    }

    .pm-tx div {
      flex:1;text-align:left;min-width:0
    }

    .pm-tx b {
      font-size:13.5px;font-weight:700
    }

    .pm-tx small {
      display:block;font-size:11px;color:#94a3b8
    }

    .pm-tx em {
      font-style:normal;font-weight:800;font-size:13px
    }

    .pm-tx em.pos {
      color:#16a34a
    }

    .pm-screen.dark {
      background:#0f172a;color:#e2e8f0
    }

    .pm-screen.dark .pm-actions button {
      background:#1e293b;color:#a5b4fc
    }

    .pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small {
      color:#64748b
    }
  </style>
</head>
<body>
  <div class="text-center">
    <div class="relative w-[270px] h-[560px] bg-[#0b1220] rounded-[46px] p-[13px] shadow-[0_30px_60px_-20px_rgba(15,23,42,.5),inset_0_0_0_2px_#1e293b]" id="pmPhone">
      <span class="absolute top-6 left-1/2 [transform:translateX(-50%)] w-[88px] h-[26px] bg-[#000] rounded-[14px] z-[5]"></span>
      <span class="absolute bg-[#0b1220] rounded-[3px] left-[-3px] top-[130px] w-[3px] h-[34px]"></span>
      <span class="absolute bg-[#0b1220] rounded-[3px] left-[-3px] top-44 w-[3px] h-[34px]"></span>
      <span class="absolute bg-[#0b1220] rounded-[3px] right-[-3px] top-[150px] w-[3px] h-[54px]"></span>
      <div class="pm-screen relative w-full h-full rounded-[34px] overflow-hidden bg-[#f1f5f9] text-[#0f172a] [transition:background_.3s,color_.3s]">
        <div class="flex justify-between items-center pt-3.5 px-[22px] pb-1 text-[13px] font-bold [font-variant-numeric:tabular-nums]">
          <span id="pmTime">9:41</span>
          <span class="flex items-center gap-[5px] fill-current">
            <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
            <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" stroke-width="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
            <span class="pm-batt w-[22px] h-[11px] border rounded-[3px] relative inline-block opacity-90"><i></i></span>
          </span>
        </div>
        <div class="pt-2 px-[18px] pb-[18px] h-[calc(100% - 36px)] overflow-y-auto">
          <header class="pm-head flex items-center justify-between mt-2 mx-0 mb-4">
            <div><small>Good morning</small><h2>Alex</h2></div>
            <div class="w-10 h-10 rounded-full [background:linear-gradient(135deg,#6366f1,#22d3ee)] text-[#fff] flex items-center justify-center font-extrabold">A</div>
          </header>
          <div class="pm-balance [background:linear-gradient(135deg,#4f46e5,#7c3aed)] text-[#fff] rounded-[18px] p-[18px] text-left mb-3.5">
            <small>Total balance</small>
            <strong>$12,480.55</strong>
            <span class="text-xs bg-[rgba(255,255,255,.2)] py-0.5 px-2 rounded-[99px]">+2.4% this week</span>
          </div>
          <div class="pm-actions flex gap-2 mb-[18px]">
            <button>Send</button><button>Request</button><button>Top up</button>
          </div>
          <div class="flex flex-col gap-[13px]">
            <div class="pm-tx flex items-center gap-[11px]"><span class="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style="background:#6366f1">N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
            <div class="pm-tx flex items-center gap-[11px]"><span class="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style="background:#22c55e">S</span><div><b>Salary</b><small>Acme Inc</small></div><em class="pos">+$4,200</em></div>
            <div class="pm-tx flex items-center gap-[11px]"><span class="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style="background:#f59e0b">U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
            <div class="pm-tx flex items-center gap-[11px]"><span class="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style="background:#ec4899">D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
          </div>
        </div>
      </div>
    </div>
    <button type="button" class="mt-[22px] bg-[#0f172a] text-[#fff] border-0 rounded-[9px] py-[9px] px-4 text-[12.5px] font-bold cursor-pointer font-[inherit] hover:bg-[#1e293b]" id="pmToggle">Toggle dark screen</button>
  </div>
  <script>
    var screen = document.querySelector('.pm-screen');
    var timeEl = document.getElementById('pmTime');
    
    // Live status-bar clock so the mockup feels real.
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
    }
    tick();
    setInterval(tick, 10000);
    
    document.getElementById('pmToggle').addEventListener('click', function () {
      screen.classList.toggle('dark');
    });
  </script>
</body>
</html>
React
import React, { useEffect } from 'react';

// CSS — optionally move to PhoneMockup.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.pm-stage{text-align:center}

.pm-phone{position:relative;width:270px;height:560px;background:#0b1220;border-radius:46px;padding:13px;box-shadow:0 30px 60px -20px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b}
.pm-island{position:absolute;top:24px;left:50%;transform:translateX(-50%);width:88px;height:26px;background:#000;border-radius:14px;z-index:5}
.pm-btn{position:absolute;background:#0b1220;border-radius:3px}
.pm-vol-up{left:-3px;top:130px;width:3px;height:34px}
.pm-vol-dn{left:-3px;top:176px;width:3px;height:34px}
.pm-power{right:-3px;top:150px;width:3px;height:54px}

.pm-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;background:#f1f5f9;color:#0f172a;transition:background .3s,color .3s}
.pm-status{display:flex;justify-content:space-between;align-items:center;padding:14px 22px 4px;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.pm-status-r{display:flex;align-items:center;gap:5px;fill:currentColor}
.pm-batt{width:22px;height:11px;border:1.4px solid currentColor;border-radius:3px;position:relative;display:inline-block;opacity:.9}
.pm-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0}
.pm-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px}

.pm-app{padding:8px 18px 18px;height:calc(100% - 36px);overflow-y:auto}
.pm-head{display:flex;align-items:center;justify-content:space-between;margin:8px 0 16px}
.pm-head small{font-size:12px;color:#94a3b8;font-weight:600}
.pm-head h2{font-size:20px;font-weight:800}
.pm-ava{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800}

.pm-balance{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;border-radius:18px;padding:18px;text-align:left;margin-bottom:14px}
.pm-balance small{opacity:.8;font-size:12px}
.pm-balance strong{display:block;font-size:27px;font-weight:800;margin:3px 0}
.pm-up{font-size:12px;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:99px}

.pm-actions{display:flex;gap:8px;margin-bottom:18px}
.pm-actions button{flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)}

.pm-list{display:flex;flex-direction:column;gap:13px}
.pm-tx{display:flex;align-items:center;gap:11px}
.pm-ic{width:38px;height:38px;border-radius:11px;color:#fff;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.pm-tx div{flex:1;text-align:left;min-width:0}
.pm-tx b{font-size:13.5px;font-weight:700}
.pm-tx small{display:block;font-size:11px;color:#94a3b8}
.pm-tx em{font-style:normal;font-weight:800;font-size:13px}
.pm-tx em.pos{color:#16a34a}

.pm-screen.dark{background:#0f172a;color:#e2e8f0}
.pm-screen.dark .pm-actions button{background:#1e293b;color:#a5b4fc}
.pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small{color:#64748b}

.pm-toggle{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.pm-toggle:hover{background:#1e293b}
`;

export default function PhoneMockup() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    var screen = document.querySelector('.pm-screen');
    var timeEl = document.getElementById('pmTime');
    
    // Live status-bar clock so the mockup feels real.
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
    }
    tick();
    setInterval(tick, 10000);
    
    document.getElementById('pmToggle').addEventListener('click', function () {
      screen.classList.toggle('dark');
    });
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };
  }, []);

  return (
    <>
      <style>{css}</style>
      <div className="pm-stage">
        <div className="pm-phone" id="pmPhone">
          <span className="pm-island"></span>
          <span className="pm-btn pm-vol-up"></span>
          <span className="pm-btn pm-vol-dn"></span>
          <span className="pm-btn pm-power"></span>
          <div className="pm-screen">
            <div className="pm-status">
              <span id="pmTime">9:41</span>
              <span className="pm-status-r">
                <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
                <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" strokeWidth="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
                <span className="pm-batt"><i></i></span>
              </span>
            </div>
            <div className="pm-app">
              <header className="pm-head">
                <div><small>Good morning</small><h2>Alex</h2></div>
                <div className="pm-ava">A</div>
              </header>
              <div className="pm-balance">
                <small>Total balance</small>
                <strong>$12,480.55</strong>
                <span className="pm-up">+2.4% this week</span>
              </div>
              <div className="pm-actions">
                <button>Send</button><button>Request</button><button>Top up</button>
              </div>
              <div className="pm-list">
                <div className="pm-tx"><span className="pm-ic" style={{ background: '#6366f1' }}>N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
                <div className="pm-tx"><span className="pm-ic" style={{ background: '#22c55e' }}>S</span><div><b>Salary</b><small>Acme Inc</small></div><em className="pos">+$4,200</em></div>
                <div className="pm-tx"><span className="pm-ic" style={{ background: '#f59e0b' }}>U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
                <div className="pm-tx"><span className="pm-ic" style={{ background: '#ec4899' }}>D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
              </div>
            </div>
          </div>
        </div>
        <button type="button" className="pm-toggle" id="pmToggle">Toggle dark screen</button>
      </div>
    </>
  );
}
React + Tailwind
import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+

export default function PhoneMockup() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    var screen = document.querySelector('.pm-screen');
    var timeEl = document.getElementById('pmTime');
    
    // Live status-bar clock so the mockup feels real.
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
    }
    tick();
    setInterval(tick, 10000);
    
    document.getElementById('pmToggle').addEventListener('click', function () {
      screen.classList.toggle('dark');
    });
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };
  }, []);

  return (
    <>
      <style>{`
* {
  box-sizing:border-box;margin:0;padding:0
}

body {
  font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px
}

.pm-batt::after {
  content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0
}

.pm-batt i {
  position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px
}

.pm-head small {
  font-size:12px;color:#94a3b8;font-weight:600
}

.pm-head h2 {
  font-size:20px;font-weight:800
}

.pm-balance small {
  opacity:.8;font-size:12px
}

.pm-balance strong {
  display:block;font-size:27px;font-weight:800;margin:3px 0
}

.pm-actions button {
  flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)
}

.pm-tx div {
  flex:1;text-align:left;min-width:0
}

.pm-tx b {
  font-size:13.5px;font-weight:700
}

.pm-tx small {
  display:block;font-size:11px;color:#94a3b8
}

.pm-tx em {
  font-style:normal;font-weight:800;font-size:13px
}

.pm-tx em.pos {
  color:#16a34a
}

.pm-screen.dark {
  background:#0f172a;color:#e2e8f0
}

.pm-screen.dark .pm-actions button {
  background:#1e293b;color:#a5b4fc
}

.pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small {
  color:#64748b
}
      `}</style>
      <div className="text-center">
        <div className="relative w-[270px] h-[560px] bg-[#0b1220] rounded-[46px] p-[13px] shadow-[0_30px_60px_-20px_rgba(15,23,42,.5),inset_0_0_0_2px_#1e293b]" id="pmPhone">
          <span className="absolute top-6 left-1/2 [transform:translateX(-50%)] w-[88px] h-[26px] bg-[#000] rounded-[14px] z-[5]"></span>
          <span className="absolute bg-[#0b1220] rounded-[3px] left-[-3px] top-[130px] w-[3px] h-[34px]"></span>
          <span className="absolute bg-[#0b1220] rounded-[3px] left-[-3px] top-44 w-[3px] h-[34px]"></span>
          <span className="absolute bg-[#0b1220] rounded-[3px] right-[-3px] top-[150px] w-[3px] h-[54px]"></span>
          <div className="pm-screen relative w-full h-full rounded-[34px] overflow-hidden bg-[#f1f5f9] text-[#0f172a] [transition:background_.3s,color_.3s]">
            <div className="flex justify-between items-center pt-3.5 px-[22px] pb-1 text-[13px] font-bold [font-variant-numeric:tabular-nums]">
              <span id="pmTime">9:41</span>
              <span className="flex items-center gap-[5px] fill-current">
                <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
                <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" strokeWidth="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
                <span className="pm-batt w-[22px] h-[11px] border rounded-[3px] relative inline-block opacity-90"><i></i></span>
              </span>
            </div>
            <div className="pt-2 px-[18px] pb-[18px] h-[calc(100% - 36px)] overflow-y-auto">
              <header className="pm-head flex items-center justify-between mt-2 mx-0 mb-4">
                <div><small>Good morning</small><h2>Alex</h2></div>
                <div className="w-10 h-10 rounded-full [background:linear-gradient(135deg,#6366f1,#22d3ee)] text-[#fff] flex items-center justify-center font-extrabold">A</div>
              </header>
              <div className="pm-balance [background:linear-gradient(135deg,#4f46e5,#7c3aed)] text-[#fff] rounded-[18px] p-[18px] text-left mb-3.5">
                <small>Total balance</small>
                <strong>$12,480.55</strong>
                <span className="text-xs bg-[rgba(255,255,255,.2)] py-0.5 px-2 rounded-[99px]">+2.4% this week</span>
              </div>
              <div className="pm-actions flex gap-2 mb-[18px]">
                <button>Send</button><button>Request</button><button>Top up</button>
              </div>
              <div className="flex flex-col gap-[13px]">
                <div className="pm-tx flex items-center gap-[11px]"><span className="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style={{ background: '#6366f1' }}>N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
                <div className="pm-tx flex items-center gap-[11px]"><span className="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style={{ background: '#22c55e' }}>S</span><div><b>Salary</b><small>Acme Inc</small></div><em className="pos">+$4,200</em></div>
                <div className="pm-tx flex items-center gap-[11px]"><span className="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style={{ background: '#f59e0b' }}>U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
                <div className="pm-tx flex items-center gap-[11px]"><span className="w-[38px] h-[38px] rounded-[11px] text-[#fff] font-extrabold flex items-center justify-center shrink-0" style={{ background: '#ec4899' }}>D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
              </div>
            </div>
          </div>
        </div>
        <button type="button" className="mt-[22px] bg-[#0f172a] text-[#fff] border-0 rounded-[9px] py-[9px] px-4 text-[12.5px] font-bold cursor-pointer font-[inherit] hover:bg-[#1e293b]" id="pmToggle">Toggle dark screen</button>
      </div>
    </>
  );
}
Vue
<template>
  <div class="pm-stage">
    <div class="pm-phone" id="pmPhone">
      <span class="pm-island"></span>
      <span class="pm-btn pm-vol-up"></span>
      <span class="pm-btn pm-vol-dn"></span>
      <span class="pm-btn pm-power"></span>
      <div class="pm-screen">
        <div class="pm-status">
          <span id="pmTime">9:41</span>
          <span class="pm-status-r">
            <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
            <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" stroke-width="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
            <span class="pm-batt"><i></i></span>
          </span>
        </div>
        <div class="pm-app">
          <header class="pm-head">
            <div><small>Good morning</small><h2>Alex</h2></div>
            <div class="pm-ava">A</div>
          </header>
          <div class="pm-balance">
            <small>Total balance</small>
            <strong>$12,480.55</strong>
            <span class="pm-up">+2.4% this week</span>
          </div>
          <div class="pm-actions">
            <button>Send</button><button>Request</button><button>Top up</button>
          </div>
          <div class="pm-list">
            <div class="pm-tx"><span class="pm-ic" style="background:#6366f1">N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#22c55e">S</span><div><b>Salary</b><small>Acme Inc</small></div><em class="pos">+$4,200</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#f59e0b">U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
            <div class="pm-tx"><span class="pm-ic" style="background:#ec4899">D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
          </div>
        </div>
      </div>
    </div>
    <button type="button" class="pm-toggle" id="pmToggle">Toggle dark screen</button>
  </div>
</template>

<script setup>
import { onMounted } from 'vue';

let screen;
let timeEl;
// Live status-bar clock so the mockup feels real.
function tick() {
  var d = new Date();
  var h = d.getHours() % 12 || 12;
  timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
}

onMounted(() => {
  screen = document.querySelector('.pm-screen');
  timeEl = document.getElementById('pmTime');
  tick();
  setInterval(tick, 10000);
  document.getElementById('pmToggle').addEventListener('click', function () {
    screen.classList.toggle('dark');
  });
});
</script>

<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.pm-stage{text-align:center}

.pm-phone{position:relative;width:270px;height:560px;background:#0b1220;border-radius:46px;padding:13px;box-shadow:0 30px 60px -20px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b}
.pm-island{position:absolute;top:24px;left:50%;transform:translateX(-50%);width:88px;height:26px;background:#000;border-radius:14px;z-index:5}
.pm-btn{position:absolute;background:#0b1220;border-radius:3px}
.pm-vol-up{left:-3px;top:130px;width:3px;height:34px}
.pm-vol-dn{left:-3px;top:176px;width:3px;height:34px}
.pm-power{right:-3px;top:150px;width:3px;height:54px}

.pm-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;background:#f1f5f9;color:#0f172a;transition:background .3s,color .3s}
.pm-status{display:flex;justify-content:space-between;align-items:center;padding:14px 22px 4px;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.pm-status-r{display:flex;align-items:center;gap:5px;fill:currentColor}
.pm-batt{width:22px;height:11px;border:1.4px solid currentColor;border-radius:3px;position:relative;display:inline-block;opacity:.9}
.pm-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0}
.pm-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px}

.pm-app{padding:8px 18px 18px;height:calc(100% - 36px);overflow-y:auto}
.pm-head{display:flex;align-items:center;justify-content:space-between;margin:8px 0 16px}
.pm-head small{font-size:12px;color:#94a3b8;font-weight:600}
.pm-head h2{font-size:20px;font-weight:800}
.pm-ava{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800}

.pm-balance{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;border-radius:18px;padding:18px;text-align:left;margin-bottom:14px}
.pm-balance small{opacity:.8;font-size:12px}
.pm-balance strong{display:block;font-size:27px;font-weight:800;margin:3px 0}
.pm-up{font-size:12px;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:99px}

.pm-actions{display:flex;gap:8px;margin-bottom:18px}
.pm-actions button{flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)}

.pm-list{display:flex;flex-direction:column;gap:13px}
.pm-tx{display:flex;align-items:center;gap:11px}
.pm-ic{width:38px;height:38px;border-radius:11px;color:#fff;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.pm-tx div{flex:1;text-align:left;min-width:0}
.pm-tx b{font-size:13.5px;font-weight:700}
.pm-tx small{display:block;font-size:11px;color:#94a3b8}
.pm-tx em{font-style:normal;font-weight:800;font-size:13px}
.pm-tx em.pos{color:#16a34a}

.pm-screen.dark{background:#0f172a;color:#e2e8f0}
.pm-screen.dark .pm-actions button{background:#1e293b;color:#a5b4fc}
.pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small{color:#64748b}

.pm-toggle{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.pm-toggle:hover{background:#1e293b}
</style>
Angular
// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-phone-mockup',
  standalone: true,
  imports: [CommonModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="pm-stage">
      <div class="pm-phone" id="pmPhone">
        <span class="pm-island"></span>
        <span class="pm-btn pm-vol-up"></span>
        <span class="pm-btn pm-vol-dn"></span>
        <span class="pm-btn pm-power"></span>
        <div class="pm-screen">
          <div class="pm-status">
            <span id="pmTime">9:41</span>
            <span class="pm-status-r">
              <svg viewBox="0 0 18 12" width="17"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="4" y="5" width="3" height="7" rx="1"/><rect x="8" y="3" width="3" height="9" rx="1"/><rect x="12" y="1" width="3" height="11" rx="1" opacity=".35"/></svg>
              <svg viewBox="0 0 16 12" width="15"><path d="M8 11.5 1 4.5a10 10 0 0 1 14 0z" fill="none" stroke="currentColor" stroke-width="1.4"/><circle cx="8" cy="9.4" r="1.3"/></svg>
              <span class="pm-batt"><i></i></span>
            </span>
          </div>
          <div class="pm-app">
            <header class="pm-head">
              <div><small>Good morning</small><h2>Alex</h2></div>
              <div class="pm-ava">A</div>
            </header>
            <div class="pm-balance">
              <small>Total balance</small>
              <strong>$12,480.55</strong>
              <span class="pm-up">+2.4% this week</span>
            </div>
            <div class="pm-actions">
              <button>Send</button><button>Request</button><button>Top up</button>
            </div>
            <div class="pm-list">
              <div class="pm-tx"><span class="pm-ic" style="background:#6366f1">N</span><div><b>Netflix</b><small>Subscription</small></div><em>-$15.99</em></div>
              <div class="pm-tx"><span class="pm-ic" style="background:#22c55e">S</span><div><b>Salary</b><small>Acme Inc</small></div><em class="pos">+$4,200</em></div>
              <div class="pm-tx"><span class="pm-ic" style="background:#f59e0b">U</span><div><b>Uber</b><small>Transport</small></div><em>-$8.40</em></div>
              <div class="pm-tx"><span class="pm-ic" style="background:#ec4899">D</span><div><b>Dribbble</b><small>Pro plan</small></div><em>-$60.00</em></div>
            </div>
          </div>
        </div>
      </div>
      <button type="button" class="pm-toggle" id="pmToggle">Toggle dark screen</button>
    </div>
  `,
  styles: [`
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
    .pm-stage{text-align:center}
    
    .pm-phone{position:relative;width:270px;height:560px;background:#0b1220;border-radius:46px;padding:13px;box-shadow:0 30px 60px -20px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b}
    .pm-island{position:absolute;top:24px;left:50%;transform:translateX(-50%);width:88px;height:26px;background:#000;border-radius:14px;z-index:5}
    .pm-btn{position:absolute;background:#0b1220;border-radius:3px}
    .pm-vol-up{left:-3px;top:130px;width:3px;height:34px}
    .pm-vol-dn{left:-3px;top:176px;width:3px;height:34px}
    .pm-power{right:-3px;top:150px;width:3px;height:54px}
    
    .pm-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;background:#f1f5f9;color:#0f172a;transition:background .3s,color .3s}
    .pm-status{display:flex;justify-content:space-between;align-items:center;padding:14px 22px 4px;font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
    .pm-status-r{display:flex;align-items:center;gap:5px;fill:currentColor}
    .pm-batt{width:22px;height:11px;border:1.4px solid currentColor;border-radius:3px;position:relative;display:inline-block;opacity:.9}
    .pm-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:currentColor;border-radius:0 1px 1px 0}
    .pm-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:62%;background:currentColor;border-radius:1px}
    
    .pm-app{padding:8px 18px 18px;height:calc(100% - 36px);overflow-y:auto}
    .pm-head{display:flex;align-items:center;justify-content:space-between;margin:8px 0 16px}
    .pm-head small{font-size:12px;color:#94a3b8;font-weight:600}
    .pm-head h2{font-size:20px;font-weight:800}
    .pm-ava{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#22d3ee);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800}
    
    .pm-balance{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;border-radius:18px;padding:18px;text-align:left;margin-bottom:14px}
    .pm-balance small{opacity:.8;font-size:12px}
    .pm-balance strong{display:block;font-size:27px;font-weight:800;margin:3px 0}
    .pm-up{font-size:12px;background:rgba(255,255,255,.2);padding:2px 8px;border-radius:99px}
    
    .pm-actions{display:flex;gap:8px;margin-bottom:18px}
    .pm-actions button{flex:1;border:none;background:#fff;color:#4338ca;border-radius:12px;padding:10px;font-size:12px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 4px 12px -6px rgba(0,0,0,.3)}
    
    .pm-list{display:flex;flex-direction:column;gap:13px}
    .pm-tx{display:flex;align-items:center;gap:11px}
    .pm-ic{width:38px;height:38px;border-radius:11px;color:#fff;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .pm-tx div{flex:1;text-align:left;min-width:0}
    .pm-tx b{font-size:13.5px;font-weight:700}
    .pm-tx small{display:block;font-size:11px;color:#94a3b8}
    .pm-tx em{font-style:normal;font-weight:800;font-size:13px}
    .pm-tx em.pos{color:#16a34a}
    
    .pm-screen.dark{background:#0f172a;color:#e2e8f0}
    .pm-screen.dark .pm-actions button{background:#1e293b;color:#a5b4fc}
    .pm-screen.dark .pm-tx small,.pm-screen.dark .pm-head small{color:#64748b}
    
    .pm-toggle{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
    .pm-toggle:hover{background:#1e293b}
  `]
})
export class PhoneMockupComponent implements AfterViewInit {
  ngAfterViewInit(): void {
    var screen = document.querySelector('.pm-screen');
    var timeEl = document.getElementById('pmTime');
    
    // Live status-bar clock so the mockup feels real.
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
    }
    tick();
    setInterval(tick, 10000);
    
    document.getElementById('pmToggle').addEventListener('click', function () {
      screen.classList.toggle('dark');
    });

    // Bind inline-handler functions to the component so the template can call them
    Object.assign(this, { tick });
  }
}