Mobile Lock Screen — Free CSS Phone Lock Screen Snippet

Mobile Lock Screen · Layouts · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Live clock and date
Updates from the system clock with custom formatting.
System-style type
Thin, tight clock to match a real lock screen.
Glass notifications
Frosted cards via backdrop-filter blur.
Staggered entrance
Cards cascade in with per-item delays.
Gradient wallpaper
Multi-stop gradient plus a radial highlight.
Swipe-up gesture
Threshold-based pointer detection to unlock.
Unlock animation
Screen slides up and fades on unlock.
No dependency
Pure HTML/CSS/JS inside a CSS phone frame.

About this UI Snippet

Mobile Lock Screen — Live Clock & Notification Stack

Screenshot of the Mobile Lock Screen snippet rendered live

A mobile lock screen is the first screen a phone shows — a big clock, the date, a stack of notifications, and a swipe-to-unlock hint. It's a great showcase for app concepts, push-notification designs, and onboarding flows. This snippet builds a polished one inside a CSS phone frame with a live clock and a real swipe-up gesture, in HTML, CSS, and vanilla JavaScript with no dependency.

Live clock and date

A tick() function formats the current time in 12-hour style and writes it to both the large clock and the status bar, and builds a friendly date ("Monday, June 29") from DAYS and MONTHS arrays rather than a locale string, so the format is fully in your control. An interval keeps it current. The large time uses a thin font-weight: 300 with negative letter-spacing to mimic the system lock-screen typography.

Glassmorphism notifications

Each notification is a frosted card — a semi-transparent white background plus backdrop-filter: blur(10px) — layered over the gradient wallpaper, which is the modern lock-screen look. They animate in with a staggered lsIn keyframe (increasing animation-delay per card) so the stack cascades on load, and the list scrolls independently with overflow-y:auto.

The wallpaper

The background is a multi-stop linear-gradient with a soft radial-gradient highlight layered via ::after for depth, giving a rich wallpaper without an image. All the foreground text and glyphs are white over it, with the notification glass providing contrast for readability.

A real swipe-up gesture

Unlocking works two ways: clicking the grip, or an actual swipe — pointerdown records the start Y, and pointerup checks whether the finger moved up more than 60px before triggering unlock(). The unlock animates the whole screen up and fades it, then resets, simulating the transition into the home screen. This is the same threshold-based swipe detection used for sheets and dismissible cards.

Reusing it

Swap the notifications for your own data, change the wallpaper gradient, and keep the frame as a wrapper. It pairs naturally with a phone mockup for presenting an app, or as the entry point before a mobile login screen.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA phone lock screen renders with a live clock and notifications.
  2. 2
    Watch the clockThe time and date update live from the system clock.
  3. 3
    See the notificationsGlassy cards cascade in over the gradient wallpaper.
  4. 4
    Swipe upDrag up from the screen to trigger the unlock animation.
  5. 5
    Or tap the gripClicking the swipe hint unlocks too.
  6. 6
    Customize itSwap notifications and the wallpaper gradient for your app.

Real-world uses

Common Use Cases

App concept demos
Present a concept beside a phone mockup.
Push notification design
Show alerts like a notification center.
Onboarding entry
Lead into a mobile login screen.
Marketing screenshots
Frame an app launch in a product hero.
Wallpaper and theme demos
Learning swipe gestures
A reference for threshold-based pointer swipes.

Got questions?

Frequently Asked Questions

A pointerdown handler records the starting Y coordinate, and pointerup checks whether the pointer moved upward by more than 60 pixels — if so it calls unlock(), which slides the screen up and fades it before resetting. Clicking the grip triggers the same function. It's the same threshold-based swipe detection used for bottom sheets and dismissible cards.

Yes. A tick() function reads the current Date, formats the time in 12-hour style, and updates both the large clock and the status bar, plus builds the date string from day and month name arrays. An interval keeps it current, so the lock screen never looks frozen on a placeholder time.

Each card has a semi-transparent white background combined with backdrop-filter: blur(10px), so the gradient wallpaper shows through softly — the glassmorphism look used on real lock screens. They animate in with a staggered keyframe, increasing the animation-delay per card so the stack cascades rather than appearing all at once.

Definitely. The wallpaper is a CSS gradient with a radial highlight, so change the colors or swap in a background image. The notifications are plain markup driven by an icon, title, time, and message, so replace them with your own data or render them from an array in a framework.

Keep the time in state updated by an interval in a mount effect with cleanup, and render notifications from an array. Implement unlock as a state flag that toggles the slide-and-fade classes. The swipe uses pointer events on a ref. In Tailwind, build the wallpaper with a gradient and the glass cards with bg-white/15 and backdrop-blur.

Mobile Lock Screen — 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>Mobile Lock Screen</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#1e293b;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
    
    .ls-phone{width:280px;height:580px;background:#0b1220;border-radius:46px;padding:12px;box-shadow:0 30px 60px -20px rgba(0,0,0,.6),inset 0 0 0 2px #1e293b}
    .ls-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;color:#fff;display:flex;flex-direction:column;background:linear-gradient(160deg,#4338ca,#7c3aed 45%,#db2777)}
    .ls-screen::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none}
    
    .ls-status{display:flex;justify-content:space-between;align-items:center;padding:13px 24px 0;font-size:13px;font-weight:700;z-index:2}
    .ls-batt{width:22px;height:11px;border:1.4px solid #fff;border-radius:3px;position:relative;display:inline-block;opacity:.9}
    .ls-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:#fff;border-radius:0 1px 1px 0}
    .ls-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px}
    
    .ls-clock{text-align:center;padding:34px 0 18px;z-index:2}
    .ls-lock{display:inline-flex;opacity:.85;margin-bottom:10px}
    .ls-date{font-size:14px;font-weight:600;opacity:.92}
    .ls-time{font-size:74px;font-weight:300;letter-spacing:-2px;line-height:1;font-variant-numeric:tabular-nums}
    
    .ls-notes{flex:1;overflow-y:auto;padding:6px 14px;display:flex;flex-direction:column;gap:9px;z-index:2}
    .ls-note{display:flex;gap:10px;background:rgba(255,255,255,.16);backdrop-filter:blur(10px);border-radius:16px;padding:11px 13px;animation:lsIn .5s ease backwards}
    .ls-note:nth-child(2){animation-delay:.08s}.ls-note:nth-child(3){animation-delay:.16s}
    @keyframes lsIn{from{opacity:0;transform:translateY(10px)}}
    .ls-ic{width:30px;height:30px;border-radius:8px;color:#fff;font-weight:800;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .ls-body{flex:1;min-width:0}
    .ls-row{display:flex;justify-content:space-between;align-items:baseline}
    .ls-row b{font-size:13px;font-weight:700}
    .ls-row small{font-size:11px;opacity:.75}
    .ls-body p{font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35}
    
    .ls-foot{display:flex;align-items:center;justify-content:space-between;padding:14px 28px 22px;z-index:2}
    .ls-torch,.ls-cam{width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer}
    .ls-torch:active,.ls-cam:active{background:rgba(0,0,0,.5)}
    .ls-swipe{flex:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}
    .ls-grip{width:90px;height:5px;border-radius:3px;background:rgba(255,255,255,.85)}
    .ls-swipe span{font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite}
    @keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}
  </style>
</head>
<body>
  <div class="ls-phone">
    <div class="ls-screen" id="lsScreen">
      <div class="ls-status"><span id="lsBar">9:41</span><span class="ls-sr"><span class="ls-batt"><i></i></span></span></div>
      <div class="ls-clock">
        <span class="ls-lock"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
        <div class="ls-date" id="lsDate">Monday, June 29</div>
        <div class="ls-time" id="lsTime">9:41</div>
      </div>
      <div class="ls-notes" id="lsNotes">
        <div class="ls-note">
          <span class="ls-ic" style="background:#22c55e">M</span>
          <div class="ls-body"><div class="ls-row"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
        </div>
        <div class="ls-note">
          <span class="ls-ic" style="background:#3b82f6">C</span>
          <div class="ls-body"><div class="ls-row"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
        </div>
        <div class="ls-note">
          <span class="ls-ic" style="background:#f59e0b">W</span>
          <div class="ls-body"><div class="ls-row"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
        </div>
      </div>
      <div class="ls-foot">
        <span class="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
        <div class="ls-swipe"><div class="ls-grip"></div><span>Swipe up to unlock</span></div>
        <span class="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
      </div>
    </div>
  </div>
  <script>
    var timeEl = document.getElementById('lsTime');
    var barEl = document.getElementById('lsBar');
    var dateEl = document.getElementById('lsDate');
    var screen = document.getElementById('lsScreen');
    
    var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
    var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
      timeEl.textContent = t;
      barEl.textContent = t;
      dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
    }
    tick();
    setInterval(tick, 5000);
    
    // Swipe up (or click the grip) "unlocks" — slides the screen content up and fades.
    var notes = document.getElementById('lsNotes');
    function unlock() {
      screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
      screen.style.transform = 'translateY(-12%)';
      screen.style.opacity = '0';
      setTimeout(function () {
        screen.style.transition = 'none';
        screen.style.transform = 'translateY(8%)';
        setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
      }, 650);
    }
    document.querySelector('.ls-swipe').addEventListener('click', unlock);
    
    var startY = null;
    screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
    screen.addEventListener('pointerup', function (e) {
      if (startY !== null && startY - e.clientY > 60) unlock();
      startY = null;
    });
  </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>Mobile Lock Screen</title>
  <!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    @keyframes lsIn{from{opacity:0;transform:translateY(10px)}}

    @keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}

    * {
      box-sizing:border-box;margin:0;padding:0
    }

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

    .ls-screen::after {
      content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none
    }

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

    .ls-batt i {
      position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px
    }

    .ls-note:nth-child(2) {
      animation-delay:.08s
    }

    .ls-note:nth-child(3) {
      animation-delay:.16s
    }

    .ls-row b {
      font-size:13px;font-weight:700
    }

    .ls-row small {
      font-size:11px;opacity:.75
    }

    .ls-body p {
      font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35
    }

    .ls-torch,.ls-cam {
      width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer
    }

    .ls-torch:active,.ls-cam:active {
      background:rgba(0,0,0,.5)
    }

    .ls-swipe span {
      font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite
    }
  </style>
</head>
<body>
  <div class="w-[280px] h-[580px] bg-[#0b1220] rounded-[46px] p-3 shadow-[0_30px_60px_-20px_rgba(0,0,0,.6),inset_0_0_0_2px_#1e293b]">
    <div class="ls-screen relative w-full h-full rounded-[34px] overflow-hidden text-[#fff] flex flex-col [background:linear-gradient(160deg,#4338ca,#7c3aed_45%,#db2777)]" id="lsScreen">
      <div class="flex justify-between items-center pt-[13px] px-6 pb-0 text-[13px] font-bold z-[2]"><span id="lsBar">9:41</span><span class="ls-sr"><span class="ls-batt w-[22px] h-[11px] border rounded-[3px] relative inline-block opacity-90"><i></i></span></span></div>
      <div class="text-center pt-[34px] px-0 pb-[18px] z-[2]">
        <span class="inline-flex opacity-85 mb-2.5"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
        <div class="text-sm font-semibold opacity-[.92]" id="lsDate">Monday, June 29</div>
        <div class="text-[74px] font-light tracking-[-2px] leading-none [font-variant-numeric:tabular-nums]" id="lsTime">9:41</div>
      </div>
      <div class="flex-1 overflow-y-auto py-1.5 px-3.5 flex flex-col gap-[9px] z-[2]" id="lsNotes">
        <div class="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
          <span class="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style="background:#22c55e">M</span>
          <div class="ls-body flex-1 min-w-0"><div class="ls-row flex justify-between items-baseline"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
        </div>
        <div class="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
          <span class="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style="background:#3b82f6">C</span>
          <div class="ls-body flex-1 min-w-0"><div class="ls-row flex justify-between items-baseline"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
        </div>
        <div class="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
          <span class="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style="background:#f59e0b">W</span>
          <div class="ls-body flex-1 min-w-0"><div class="ls-row flex justify-between items-baseline"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
        </div>
      </div>
      <div class="flex items-center justify-between pt-3.5 px-7 pb-[22px] z-[2]">
        <span class="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
        <div class="ls-swipe flex-1 text-center flex flex-col items-center gap-2"><div class="w-[90px] h-[5px] rounded-[3px] bg-[rgba(255,255,255,.85)]"></div><span>Swipe up to unlock</span></div>
        <span class="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
      </div>
    </div>
  </div>
  <script>
    var timeEl = document.getElementById('lsTime');
    var barEl = document.getElementById('lsBar');
    var dateEl = document.getElementById('lsDate');
    var screen = document.getElementById('lsScreen');
    
    var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
    var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
      timeEl.textContent = t;
      barEl.textContent = t;
      dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
    }
    tick();
    setInterval(tick, 5000);
    
    // Swipe up (or click the grip) "unlocks" — slides the screen content up and fades.
    var notes = document.getElementById('lsNotes');
    function unlock() {
      screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
      screen.style.transform = 'translateY(-12%)';
      screen.style.opacity = '0';
      setTimeout(function () {
        screen.style.transition = 'none';
        screen.style.transform = 'translateY(8%)';
        setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
      }, 650);
    }
    document.querySelector('.ls-swipe').addEventListener('click', unlock);
    
    var startY = null;
    screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
    screen.addEventListener('pointerup', function (e) {
      if (startY !== null && startY - e.clientY > 60) unlock();
      startY = null;
    });
  </script>
</body>
</html>
React
import React, { useEffect } from 'react';

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

.ls-phone{width:280px;height:580px;background:#0b1220;border-radius:46px;padding:12px;box-shadow:0 30px 60px -20px rgba(0,0,0,.6),inset 0 0 0 2px #1e293b}
.ls-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;color:#fff;display:flex;flex-direction:column;background:linear-gradient(160deg,#4338ca,#7c3aed 45%,#db2777)}
.ls-screen::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none}

.ls-status{display:flex;justify-content:space-between;align-items:center;padding:13px 24px 0;font-size:13px;font-weight:700;z-index:2}
.ls-batt{width:22px;height:11px;border:1.4px solid #fff;border-radius:3px;position:relative;display:inline-block;opacity:.9}
.ls-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:#fff;border-radius:0 1px 1px 0}
.ls-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px}

.ls-clock{text-align:center;padding:34px 0 18px;z-index:2}
.ls-lock{display:inline-flex;opacity:.85;margin-bottom:10px}
.ls-date{font-size:14px;font-weight:600;opacity:.92}
.ls-time{font-size:74px;font-weight:300;letter-spacing:-2px;line-height:1;font-variant-numeric:tabular-nums}

.ls-notes{flex:1;overflow-y:auto;padding:6px 14px;display:flex;flex-direction:column;gap:9px;z-index:2}
.ls-note{display:flex;gap:10px;background:rgba(255,255,255,.16);backdrop-filter:blur(10px);border-radius:16px;padding:11px 13px;animation:lsIn .5s ease backwards}
.ls-note:nth-child(2){animation-delay:.08s}.ls-note:nth-child(3){animation-delay:.16s}
@keyframes lsIn{from{opacity:0;transform:translateY(10px)}}
.ls-ic{width:30px;height:30px;border-radius:8px;color:#fff;font-weight:800;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.ls-body{flex:1;min-width:0}
.ls-row{display:flex;justify-content:space-between;align-items:baseline}
.ls-row b{font-size:13px;font-weight:700}
.ls-row small{font-size:11px;opacity:.75}
.ls-body p{font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35}

.ls-foot{display:flex;align-items:center;justify-content:space-between;padding:14px 28px 22px;z-index:2}
.ls-torch,.ls-cam{width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer}
.ls-torch:active,.ls-cam:active{background:rgba(0,0,0,.5)}
.ls-swipe{flex:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}
.ls-grip{width:90px;height:5px;border-radius:3px;background:rgba(255,255,255,.85)}
.ls-swipe span{font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite}
@keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}
`;

export default function MobileLockScreen() {
  // 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 timeEl = document.getElementById('lsTime');
    var barEl = document.getElementById('lsBar');
    var dateEl = document.getElementById('lsDate');
    var screen = document.getElementById('lsScreen');
    
    var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
    var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
      timeEl.textContent = t;
      barEl.textContent = t;
      dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
    }
    tick();
    setInterval(tick, 5000);
    
    // Swipe up (or click the grip) "unlocks" — slides the screen content up and fades.
    var notes = document.getElementById('lsNotes');
    function unlock() {
      screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
      screen.style.transform = 'translateY(-12%)';
      screen.style.opacity = '0';
      setTimeout(function () {
        screen.style.transition = 'none';
        screen.style.transform = 'translateY(8%)';
        setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
      }, 650);
    }
    document.querySelector('.ls-swipe').addEventListener('click', unlock);
    
    var startY = null;
    screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
    screen.addEventListener('pointerup', function (e) {
      if (startY !== null && startY - e.clientY > 60) unlock();
      startY = null;
    });
    // 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="ls-phone">
        <div className="ls-screen" id="lsScreen">
          <div className="ls-status"><span id="lsBar">9:41</span><span className="ls-sr"><span className="ls-batt"><i></i></span></span></div>
          <div className="ls-clock">
            <span className="ls-lock"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" strokeWidth="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
            <div className="ls-date" id="lsDate">Monday, June 29</div>
            <div className="ls-time" id="lsTime">9:41</div>
          </div>
          <div className="ls-notes" id="lsNotes">
            <div className="ls-note">
              <span className="ls-ic" style={{ background: '#22c55e' }}>M</span>
              <div className="ls-body"><div className="ls-row"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
            </div>
            <div className="ls-note">
              <span className="ls-ic" style={{ background: '#3b82f6' }}>C</span>
              <div className="ls-body"><div className="ls-row"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
            </div>
            <div className="ls-note">
              <span className="ls-ic" style={{ background: '#f59e0b' }}>W</span>
              <div className="ls-body"><div className="ls-row"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
            </div>
          </div>
          <div className="ls-foot">
            <span className="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
            <div className="ls-swipe"><div className="ls-grip"></div><span>Swipe up to unlock</span></div>
            <span className="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
          </div>
        </div>
      </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 MobileLockScreen() {
  // 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 timeEl = document.getElementById('lsTime');
    var barEl = document.getElementById('lsBar');
    var dateEl = document.getElementById('lsDate');
    var screen = document.getElementById('lsScreen');
    
    var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
    var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
      timeEl.textContent = t;
      barEl.textContent = t;
      dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
    }
    tick();
    setInterval(tick, 5000);
    
    // Swipe up (or click the grip) "unlocks" — slides the screen content up and fades.
    var notes = document.getElementById('lsNotes');
    function unlock() {
      screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
      screen.style.transform = 'translateY(-12%)';
      screen.style.opacity = '0';
      setTimeout(function () {
        screen.style.transition = 'none';
        screen.style.transform = 'translateY(8%)';
        setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
      }, 650);
    }
    document.querySelector('.ls-swipe').addEventListener('click', unlock);
    
    var startY = null;
    screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
    screen.addEventListener('pointerup', function (e) {
      if (startY !== null && startY - e.clientY > 60) unlock();
      startY = null;
    });
    // 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>{`
@keyframes lsIn{from{opacity:0;transform:translateY(10px)}}

@keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}

* {
  box-sizing:border-box;margin:0;padding:0
}

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

.ls-screen::after {
  content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none
}

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

.ls-batt i {
  position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px
}

.ls-note:nth-child(2) {
  animation-delay:.08s
}

.ls-note:nth-child(3) {
  animation-delay:.16s
}

.ls-row b {
  font-size:13px;font-weight:700
}

.ls-row small {
  font-size:11px;opacity:.75
}

.ls-body p {
  font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35
}

.ls-torch,.ls-cam {
  width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer
}

.ls-torch:active,.ls-cam:active {
  background:rgba(0,0,0,.5)
}

.ls-swipe span {
  font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite
}
      `}</style>
      <div className="w-[280px] h-[580px] bg-[#0b1220] rounded-[46px] p-3 shadow-[0_30px_60px_-20px_rgba(0,0,0,.6),inset_0_0_0_2px_#1e293b]">
        <div className="ls-screen relative w-full h-full rounded-[34px] overflow-hidden text-[#fff] flex flex-col [background:linear-gradient(160deg,#4338ca,#7c3aed_45%,#db2777)]" id="lsScreen">
          <div className="flex justify-between items-center pt-[13px] px-6 pb-0 text-[13px] font-bold z-[2]"><span id="lsBar">9:41</span><span className="ls-sr"><span className="ls-batt w-[22px] h-[11px] border rounded-[3px] relative inline-block opacity-90"><i></i></span></span></div>
          <div className="text-center pt-[34px] px-0 pb-[18px] z-[2]">
            <span className="inline-flex opacity-85 mb-2.5"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" strokeWidth="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
            <div className="text-sm font-semibold opacity-[.92]" id="lsDate">Monday, June 29</div>
            <div className="text-[74px] font-light tracking-[-2px] leading-none [font-variant-numeric:tabular-nums]" id="lsTime">9:41</div>
          </div>
          <div className="flex-1 overflow-y-auto py-1.5 px-3.5 flex flex-col gap-[9px] z-[2]" id="lsNotes">
            <div className="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
              <span className="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style={{ background: '#22c55e' }}>M</span>
              <div className="ls-body flex-1 min-w-0"><div className="ls-row flex justify-between items-baseline"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
            </div>
            <div className="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
              <span className="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style={{ background: '#3b82f6' }}>C</span>
              <div className="ls-body flex-1 min-w-0"><div className="ls-row flex justify-between items-baseline"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
            </div>
            <div className="ls-note flex gap-2.5 bg-[rgba(255,255,255,.16)] [backdrop-filter:blur(10px)] rounded-2xl py-[11px] px-[13px] [animation:lsIn_.5s_ease_backwards]">
              <span className="w-[30px] h-[30px] rounded-lg text-[#fff] font-extrabold text-[13px] flex items-center justify-center shrink-0" style={{ background: '#f59e0b' }}>W</span>
              <div className="ls-body flex-1 min-w-0"><div className="ls-row flex justify-between items-baseline"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
            </div>
          </div>
          <div className="flex items-center justify-between pt-3.5 px-7 pb-[22px] z-[2]">
            <span className="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
            <div className="ls-swipe flex-1 text-center flex flex-col items-center gap-2"><div className="w-[90px] h-[5px] rounded-[3px] bg-[rgba(255,255,255,.85)]"></div><span>Swipe up to unlock</span></div>
            <span className="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
          </div>
        </div>
      </div>
    </>
  );
}
Vue
<template>
  <div class="ls-phone">
    <div class="ls-screen" id="lsScreen">
      <div class="ls-status"><span id="lsBar">9:41</span><span class="ls-sr"><span class="ls-batt"><i></i></span></span></div>
      <div class="ls-clock">
        <span class="ls-lock"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
        <div class="ls-date" id="lsDate">Monday, June 29</div>
        <div class="ls-time" id="lsTime">9:41</div>
      </div>
      <div class="ls-notes" id="lsNotes">
        <div class="ls-note">
          <span class="ls-ic" style="background:#22c55e">M</span>
          <div class="ls-body"><div class="ls-row"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
        </div>
        <div class="ls-note">
          <span class="ls-ic" style="background:#3b82f6">C</span>
          <div class="ls-body"><div class="ls-row"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
        </div>
        <div class="ls-note">
          <span class="ls-ic" style="background:#f59e0b">W</span>
          <div class="ls-body"><div class="ls-row"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
        </div>
      </div>
      <div class="ls-foot">
        <span class="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
        <div class="ls-swipe"><div class="ls-grip"></div><span>Swipe up to unlock</span></div>
        <span class="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
      </div>
    </div>
  </div>
</template>

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

let timeEl;
let barEl;
let dateEl;
let screen;
var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
function tick() {
  var d = new Date();
  var h = d.getHours() % 12 || 12;
  var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
  timeEl.textContent = t;
  barEl.textContent = t;
  dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
}
let notes;
function unlock() {
  screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
  screen.style.transform = 'translateY(-12%)';
  screen.style.opacity = '0';
  setTimeout(function () {
    screen.style.transition = 'none';
    screen.style.transform = 'translateY(8%)';
    setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
  }, 650);
}
var startY = null;

onMounted(() => {
  timeEl = document.getElementById('lsTime');
  barEl = document.getElementById('lsBar');
  dateEl = document.getElementById('lsDate');
  screen = document.getElementById('lsScreen');
  tick();
  setInterval(tick, 5000);
  notes = document.getElementById('lsNotes');
  document.querySelector('.ls-swipe').addEventListener('click', unlock);
  screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
  screen.addEventListener('pointerup', function (e) {
    if (startY !== null && startY - e.clientY > 60) unlock();
    startY = null;
  });
});
</script>

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

.ls-phone{width:280px;height:580px;background:#0b1220;border-radius:46px;padding:12px;box-shadow:0 30px 60px -20px rgba(0,0,0,.6),inset 0 0 0 2px #1e293b}
.ls-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;color:#fff;display:flex;flex-direction:column;background:linear-gradient(160deg,#4338ca,#7c3aed 45%,#db2777)}
.ls-screen::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none}

.ls-status{display:flex;justify-content:space-between;align-items:center;padding:13px 24px 0;font-size:13px;font-weight:700;z-index:2}
.ls-batt{width:22px;height:11px;border:1.4px solid #fff;border-radius:3px;position:relative;display:inline-block;opacity:.9}
.ls-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:#fff;border-radius:0 1px 1px 0}
.ls-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px}

.ls-clock{text-align:center;padding:34px 0 18px;z-index:2}
.ls-lock{display:inline-flex;opacity:.85;margin-bottom:10px}
.ls-date{font-size:14px;font-weight:600;opacity:.92}
.ls-time{font-size:74px;font-weight:300;letter-spacing:-2px;line-height:1;font-variant-numeric:tabular-nums}

.ls-notes{flex:1;overflow-y:auto;padding:6px 14px;display:flex;flex-direction:column;gap:9px;z-index:2}
.ls-note{display:flex;gap:10px;background:rgba(255,255,255,.16);backdrop-filter:blur(10px);border-radius:16px;padding:11px 13px;animation:lsIn .5s ease backwards}
.ls-note:nth-child(2){animation-delay:.08s}.ls-note:nth-child(3){animation-delay:.16s}
@keyframes lsIn{from{opacity:0;transform:translateY(10px)}}
.ls-ic{width:30px;height:30px;border-radius:8px;color:#fff;font-weight:800;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.ls-body{flex:1;min-width:0}
.ls-row{display:flex;justify-content:space-between;align-items:baseline}
.ls-row b{font-size:13px;font-weight:700}
.ls-row small{font-size:11px;opacity:.75}
.ls-body p{font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35}

.ls-foot{display:flex;align-items:center;justify-content:space-between;padding:14px 28px 22px;z-index:2}
.ls-torch,.ls-cam{width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer}
.ls-torch:active,.ls-cam:active{background:rgba(0,0,0,.5)}
.ls-swipe{flex:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}
.ls-grip{width:90px;height:5px;border-radius:3px;background:rgba(255,255,255,.85)}
.ls-swipe span{font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite}
@keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}
</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-mobile-lock-screen',
  standalone: true,
  imports: [CommonModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="ls-phone">
      <div class="ls-screen" id="lsScreen">
        <div class="ls-status"><span id="lsBar">9:41</span><span class="ls-sr"><span class="ls-batt"><i></i></span></span></div>
        <div class="ls-clock">
          <span class="ls-lock"><svg viewBox="0 0 24 24" width="15" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>
          <div class="ls-date" id="lsDate">Monday, June 29</div>
          <div class="ls-time" id="lsTime">9:41</div>
        </div>
        <div class="ls-notes" id="lsNotes">
          <div class="ls-note">
            <span class="ls-ic" style="background:#22c55e">M</span>
            <div class="ls-body"><div class="ls-row"><b>Messages</b><small>now</small></div><p>Sara: Are we still on for lunch? 🥗</p></div>
          </div>
          <div class="ls-note">
            <span class="ls-ic" style="background:#3b82f6">C</span>
            <div class="ls-body"><div class="ls-row"><b>Calendar</b><small>9:30</small></div><p>Standup with the design team in 30 min</p></div>
          </div>
          <div class="ls-note">
            <span class="ls-ic" style="background:#f59e0b">W</span>
            <div class="ls-body"><div class="ls-row"><b>Weather</b><small>8:00</small></div><p>Sunny today, high of 24°. Light breeze.</p></div>
          </div>
        </div>
        <div class="ls-foot">
          <span class="ls-torch"><svg viewBox="0 0 24 24" width="16" fill="currentColor"><path d="M9 2h6l-1 5h-4zM10 9h4l-1 6h-2zM11 17h2v5h-2z"/></svg></span>
          <div class="ls-swipe"><div class="ls-grip"></div><span>Swipe up to unlock</span></div>
          <span class="ls-cam"><svg viewBox="0 0 24 24" width="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 8h3l1.5-2h5L16 8h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="3.2"/></svg></span>
        </div>
      </div>
    </div>
  `,
  styles: [`
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,-apple-system,sans-serif;background:#1e293b;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
    
    .ls-phone{width:280px;height:580px;background:#0b1220;border-radius:46px;padding:12px;box-shadow:0 30px 60px -20px rgba(0,0,0,.6),inset 0 0 0 2px #1e293b}
    .ls-screen{position:relative;width:100%;height:100%;border-radius:34px;overflow:hidden;color:#fff;display:flex;flex-direction:column;background:linear-gradient(160deg,#4338ca,#7c3aed 45%,#db2777)}
    .ls-screen::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 70% 18%,rgba(255,255,255,.22),transparent 45%);pointer-events:none}
    
    .ls-status{display:flex;justify-content:space-between;align-items:center;padding:13px 24px 0;font-size:13px;font-weight:700;z-index:2}
    .ls-batt{width:22px;height:11px;border:1.4px solid #fff;border-radius:3px;position:relative;display:inline-block;opacity:.9}
    .ls-batt::after{content:'';position:absolute;right:-3px;top:3px;width:2px;height:5px;background:#fff;border-radius:0 1px 1px 0}
    .ls-batt i{position:absolute;left:1.4px;top:1.4px;bottom:1.4px;width:70%;background:#fff;border-radius:1px}
    
    .ls-clock{text-align:center;padding:34px 0 18px;z-index:2}
    .ls-lock{display:inline-flex;opacity:.85;margin-bottom:10px}
    .ls-date{font-size:14px;font-weight:600;opacity:.92}
    .ls-time{font-size:74px;font-weight:300;letter-spacing:-2px;line-height:1;font-variant-numeric:tabular-nums}
    
    .ls-notes{flex:1;overflow-y:auto;padding:6px 14px;display:flex;flex-direction:column;gap:9px;z-index:2}
    .ls-note{display:flex;gap:10px;background:rgba(255,255,255,.16);backdrop-filter:blur(10px);border-radius:16px;padding:11px 13px;animation:lsIn .5s ease backwards}
    .ls-note:nth-child(2){animation-delay:.08s}.ls-note:nth-child(3){animation-delay:.16s}
    @keyframes lsIn{from{opacity:0;transform:translateY(10px)}}
    .ls-ic{width:30px;height:30px;border-radius:8px;color:#fff;font-weight:800;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .ls-body{flex:1;min-width:0}
    .ls-row{display:flex;justify-content:space-between;align-items:baseline}
    .ls-row b{font-size:13px;font-weight:700}
    .ls-row small{font-size:11px;opacity:.75}
    .ls-body p{font-size:12.5px;opacity:.95;margin-top:2px;line-height:1.35}
    
    .ls-foot{display:flex;align-items:center;justify-content:space-between;padding:14px 28px 22px;z-index:2}
    .ls-torch,.ls-cam{width:40px;height:40px;border-radius:50%;background:rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;color:#fff;cursor:pointer}
    .ls-torch:active,.ls-cam:active{background:rgba(0,0,0,.5)}
    .ls-swipe{flex:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}
    .ls-grip{width:90px;height:5px;border-radius:3px;background:rgba(255,255,255,.85)}
    .ls-swipe span{font-size:11px;opacity:.85;animation:lsPulse 2s ease-in-out infinite}
    @keyframes lsPulse{50%{opacity:.4;transform:translateY(-2px)}}
  `]
})
export class MobileLockScreenComponent implements AfterViewInit {
  ngAfterViewInit(): void {
    var timeEl = document.getElementById('lsTime');
    var barEl = document.getElementById('lsBar');
    var dateEl = document.getElementById('lsDate');
    var screen = document.getElementById('lsScreen');
    
    var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
    var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    
    function tick() {
      var d = new Date();
      var h = d.getHours() % 12 || 12;
      var t = h + ':' + ('0' + d.getMinutes()).slice(-2);
      timeEl.textContent = t;
      barEl.textContent = t;
      dateEl.textContent = DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()] + ' ' + d.getDate();
    }
    tick();
    setInterval(tick, 5000);
    
    // Swipe up (or click the grip) "unlocks" — slides the screen content up and fades.
    var notes = document.getElementById('lsNotes');
    function unlock() {
      screen.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1), opacity .5s';
      screen.style.transform = 'translateY(-12%)';
      screen.style.opacity = '0';
      setTimeout(function () {
        screen.style.transition = 'none';
        screen.style.transform = 'translateY(8%)';
        setTimeout(function () { screen.style.transition = 'transform .5s, opacity .5s'; screen.style.transform = ''; screen.style.opacity = '1'; }, 500);
      }, 650);
    }
    document.querySelector('.ls-swipe').addEventListener('click', unlock);
    
    var startY = null;
    screen.addEventListener('pointerdown', function (e) { startY = e.clientY; });
    screen.addEventListener('pointerup', function (e) {
      if (startY !== null && startY - e.clientY > 60) unlock();
      startY = null;
    });

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