More Layouts Snippets
Tablet Mockup — Free CSS iPad Device Frame Snippet
Tablet Mockup · Layouts · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Tablet Mockup — Pure-CSS iPad Frame with Rotate

A tablet mockup frames a screen design inside an iPad-style device for landing pages, portfolios, and responsive demos. This one is pure HTML and CSS — no device photo — so it stays sharp at any size and holds live, interactive content. Its standout feature is a smooth portrait-to-landscape rotate that animates the frame's dimensions, in vanilla JavaScript with no dependency.
A frame built from nested rounded boxes
The device is a dark rounded rectangle with even padding (tablets have uniform thin bezels, unlike phones), wrapping an inner screen with a smaller border-radius. The constant gap between the two radii forms the bezel, an inset box-shadow adds an edge, and a soft drop shadow lifts it off the page. A small camera dot sits centered on one bezel and repositions when the device rotates.
Animated orientation change
Rotating swaps a .portrait / .landscape class that changes the frame's width and height (300×400 ↔ 400×300), and because both are transitioned with a cubic-bezier ease, the device visibly reflows between orientations rather than snapping. The camera dot's position is also class-driven, so it moves from the side bezel to the top bezel as part of the same transition — a detail that makes the rotate feel physical.
A responsive screen inside
The screen holds a real dashboard layout — an icon sidebar plus a main area with stat cards and a CSS bar chart — built with flexbox and grid. Because it's live DOM, the content genuinely reflows inside the frame as the aspect ratio changes, which is exactly what you want when demonstrating a responsive design across orientations.
CSS-only chart and sparklines
The stat cards use gradient-filled sparkline bars and the chart is seven <span>s with :nth-child heights — no SVG or canvas — so the whole screen is lightweight and recolors with a couple of variables. It's representative filler you can swap for your own components.
Reusing it as a wrapper
Keep the device markup as a component and replace the .tm-main contents with your screen. Pair it with a phone mockup to present a responsive design on two devices at once, or drop a single screenshot in for a clean portfolio shot.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA CSS tablet renders in portrait with a dashboard screen inside.
- 2Press RotateThe frame animates between portrait and landscape dimensions.
- 3Watch the camera dotIt moves from the side bezel to the top as it rotates.
- 4See the content reflowThe dashboard layout adapts to the new aspect ratio.
- 5Drop in your screenReplace the main area with your own tablet UI.
- 6Reuse as a wrapperKeep the frame as a component with a screen slot.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Rotating toggles a portrait or landscape class that changes the frame's width and height (300×400 versus 400×300). Both dimensions have a CSS transition with a cubic-bezier ease, so the device visibly reflows between orientations instead of snapping. The camera dot's position is class-driven too, so it slides to the new bezel during the same transition.
Pure CSS. It's a dark rounded box with uniform padding wrapping a nested rounded screen; the gap between their radii is the bezel. There's no device photo, so it scales without blurring and you can recolor the frame or resize it just by changing CSS values.
Yes. The screen is live DOM built with flexbox and grid, so when the aspect ratio changes the dashboard genuinely re-lays out within the frame. That makes it useful for demonstrating a responsive design across orientations, not just showing a static picture.
Replace the contents of the .tm-main element (or the whole .tm-app) with your markup. The frame, bezels, and rotate logic are independent of the screen content, so your UI only needs to fit the screen box. Keep the device part as a reusable wrapper to swap screens easily.
Make the frame a component with an orientation prop or state that toggles the portrait/landscape class, and expose a screen slot for children. The rotate is a one-line class flip. In Tailwind, set the two orientations with conditional width/height utilities and add transition-[width,height] so they animate.
Tablet Mockup — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tablet 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}
.tm-stage{text-align:center}
.tm-tablet{position:relative;background:#0b1220;border-radius:30px;padding:16px;box-shadow:0 30px 60px -22px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b;transition:width .5s cubic-bezier(.65,0,.35,1),height .5s cubic-bezier(.65,0,.35,1)}
.tm-tablet.portrait{width:300px;height:400px}
.tm-tablet.landscape{width:400px;height:300px}
.tm-cam{position:absolute;top:50%;left:8px;width:6px;height:6px;border-radius:50%;background:#1e293b;transform:translateY(-50%)}
.tm-tablet.landscape .tm-cam{top:8px;left:50%;transform:translateX(-50%)}
.tm-screen{width:100%;height:100%;border-radius:16px;overflow:hidden;background:#f8fafc}
.tm-app{display:flex;height:100%}
.tm-side{width:52px;background:#0f172a;display:flex;flex-direction:column;align-items:center;gap:14px;padding:16px 0}
.tm-logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#22d3ee);margin-bottom:6px}
.tm-dot{width:9px;height:9px;border-radius:50%;background:#334155}
.tm-dot.on{background:#6366f1}
.tm-main{flex:1;padding:16px;text-align:left;overflow:hidden}
.tm-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.tm-head h2{font-size:17px;font-weight:800;color:#0f172a}
.tm-pill{width:64px;height:24px;border-radius:99px;background:#6366f1}
.tm-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}
.tm-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:10px}
.tm-card small{font-size:10px;color:#94a3b8;font-weight:700}
.tm-card b{display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px}
.tm-spark{display:block;height:18px;border-radius:4px;background:linear-gradient(90deg,#c7d2fe,#6366f1)}
.tm-spark.s2{background:linear-gradient(90deg,#bbf7d0,#22c55e)}
.tm-spark.s3{background:linear-gradient(90deg,#fde68a,#f59e0b)}
.tm-chart{display:flex;align-items:flex-end;gap:8px;height:90px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:12px}
.tm-chart span{flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85}
.tm-chart span:nth-child(1){height:40%}.tm-chart span:nth-child(2){height:65%}.tm-chart span:nth-child(3){height:50%}.tm-chart span:nth-child(4){height:80%}.tm-chart span:nth-child(5){height:60%}.tm-chart span:nth-child(6){height:95%}.tm-chart span:nth-child(7){height:72%}
.tm-rotate{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 18px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.tm-rotate:hover{background:#1e293b}
</style>
</head>
<body>
<div class="tm-stage">
<div class="tm-tablet portrait" id="tmTablet">
<span class="tm-cam"></span>
<div class="tm-screen">
<div class="tm-app">
<aside class="tm-side">
<div class="tm-logo"></div>
<span class="tm-dot on"></span><span class="tm-dot"></span><span class="tm-dot"></span><span class="tm-dot"></span>
</aside>
<main class="tm-main">
<header class="tm-head"><h2>Dashboard</h2><div class="tm-pill"></div></header>
<div class="tm-cards">
<div class="tm-card"><small>Revenue</small><b>$48.2k</b><i class="tm-spark"></i></div>
<div class="tm-card"><small>Users</small><b>9,310</b><i class="tm-spark s2"></i></div>
<div class="tm-card"><small>Churn</small><b>1.4%</b><i class="tm-spark s3"></i></div>
</div>
<div class="tm-chart"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" class="tm-rotate" id="tmRotate">Rotate</button>
</div>
<script>
var tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tablet 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
}
.tm-tablet.portrait {
width:300px;height:400px
}
.tm-tablet.landscape {
width:400px;height:300px
}
.tm-tablet.landscape .tm-cam {
top:8px;left:50%;transform:translateX(-50%)
}
.tm-dot.on {
background:#6366f1
}
.tm-head h2 {
font-size:17px;font-weight:800;color:#0f172a
}
.tm-card small {
font-size:10px;color:#94a3b8;font-weight:700
}
.tm-card b {
display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px
}
.tm-spark.s2 {
background:linear-gradient(90deg,#bbf7d0,#22c55e)
}
.tm-spark.s3 {
background:linear-gradient(90deg,#fde68a,#f59e0b)
}
.tm-chart span {
flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85
}
.tm-chart span:nth-child(1) {
height:40%
}
.tm-chart span:nth-child(2) {
height:65%
}
.tm-chart span:nth-child(3) {
height:50%
}
.tm-chart span:nth-child(4) {
height:80%
}
.tm-chart span:nth-child(5) {
height:60%
}
.tm-chart span:nth-child(6) {
height:95%
}
.tm-chart span:nth-child(7) {
height:72%
}
</style>
</head>
<body>
<div class="text-center">
<div class="tm-tablet relative bg-[#0b1220] rounded-[30px] p-4 shadow-[0_30px_60px_-22px_rgba(15,23,42,.5),inset_0_0_0_2px_#1e293b] [transition:width_.5s_cubic-bezier(.65,0,.35,1),height_.5s_cubic-bezier(.65,0,.35,1)] portrait" id="tmTablet">
<span class="tm-cam absolute top-1/2 left-2 w-1.5 h-1.5 rounded-full bg-[#1e293b] [transform:translateY(-50%)]"></span>
<div class="w-full h-full rounded-2xl overflow-hidden bg-[#f8fafc]">
<div class="flex h-full">
<aside class="w-[52px] bg-[#0f172a] flex flex-col items-center gap-3.5 py-4 px-0">
<div class="w-[26px] h-[26px] rounded-lg [background:linear-gradient(135deg,#6366f1,#22d3ee)] mb-1.5"></div>
<span class="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155] on"></span><span class="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span><span class="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span><span class="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span>
</aside>
<main class="flex-1 p-4 text-left overflow-hidden">
<header class="tm-head flex items-center justify-between mb-3.5"><h2>Dashboard</h2><div class="w-16 h-6 rounded-[99px] bg-[#6366f1]"></div></header>
<div class="grid grid-cols-3 gap-2.5 mb-3.5">
<div class="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Revenue</small><b>$48.2k</b><i class="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)]"></i></div>
<div class="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Users</small><b>9,310</b><i class="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)] s2"></i></div>
<div class="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Churn</small><b>1.4%</b><i class="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)] s3"></i></div>
</div>
<div class="tm-chart flex items-end gap-2 h-[90px] bg-[#fff] border border-[#e2e8f0] rounded-xl p-3"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" class="mt-[22px] bg-[#0f172a] text-[#fff] border-0 rounded-[9px] py-[9px] px-[18px] text-[12.5px] font-bold cursor-pointer font-[inherit] hover:bg-[#1e293b]" id="tmRotate">Rotate</button>
</div>
<script>
var tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to TabletMockup.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}
.tm-stage{text-align:center}
.tm-tablet{position:relative;background:#0b1220;border-radius:30px;padding:16px;box-shadow:0 30px 60px -22px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b;transition:width .5s cubic-bezier(.65,0,.35,1),height .5s cubic-bezier(.65,0,.35,1)}
.tm-tablet.portrait{width:300px;height:400px}
.tm-tablet.landscape{width:400px;height:300px}
.tm-cam{position:absolute;top:50%;left:8px;width:6px;height:6px;border-radius:50%;background:#1e293b;transform:translateY(-50%)}
.tm-tablet.landscape .tm-cam{top:8px;left:50%;transform:translateX(-50%)}
.tm-screen{width:100%;height:100%;border-radius:16px;overflow:hidden;background:#f8fafc}
.tm-app{display:flex;height:100%}
.tm-side{width:52px;background:#0f172a;display:flex;flex-direction:column;align-items:center;gap:14px;padding:16px 0}
.tm-logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#22d3ee);margin-bottom:6px}
.tm-dot{width:9px;height:9px;border-radius:50%;background:#334155}
.tm-dot.on{background:#6366f1}
.tm-main{flex:1;padding:16px;text-align:left;overflow:hidden}
.tm-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.tm-head h2{font-size:17px;font-weight:800;color:#0f172a}
.tm-pill{width:64px;height:24px;border-radius:99px;background:#6366f1}
.tm-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}
.tm-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:10px}
.tm-card small{font-size:10px;color:#94a3b8;font-weight:700}
.tm-card b{display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px}
.tm-spark{display:block;height:18px;border-radius:4px;background:linear-gradient(90deg,#c7d2fe,#6366f1)}
.tm-spark.s2{background:linear-gradient(90deg,#bbf7d0,#22c55e)}
.tm-spark.s3{background:linear-gradient(90deg,#fde68a,#f59e0b)}
.tm-chart{display:flex;align-items:flex-end;gap:8px;height:90px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:12px}
.tm-chart span{flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85}
.tm-chart span:nth-child(1){height:40%}.tm-chart span:nth-child(2){height:65%}.tm-chart span:nth-child(3){height:50%}.tm-chart span:nth-child(4){height:80%}.tm-chart span:nth-child(5){height:60%}.tm-chart span:nth-child(6){height:95%}.tm-chart span:nth-child(7){height:72%}
.tm-rotate{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 18px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.tm-rotate:hover{background:#1e293b}
`;
export default function TabletMockup() {
// 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 tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
// 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="tm-stage">
<div className="tm-tablet portrait" id="tmTablet">
<span className="tm-cam"></span>
<div className="tm-screen">
<div className="tm-app">
<aside className="tm-side">
<div className="tm-logo"></div>
<span className="tm-dot on"></span><span className="tm-dot"></span><span className="tm-dot"></span><span className="tm-dot"></span>
</aside>
<main className="tm-main">
<header className="tm-head"><h2>Dashboard</h2><div className="tm-pill"></div></header>
<div className="tm-cards">
<div className="tm-card"><small>Revenue</small><b>$48.2k</b><i className="tm-spark"></i></div>
<div className="tm-card"><small>Users</small><b>9,310</b><i className="tm-spark s2"></i></div>
<div className="tm-card"><small>Churn</small><b>1.4%</b><i className="tm-spark s3"></i></div>
</div>
<div className="tm-chart"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" className="tm-rotate" id="tmRotate">Rotate</button>
</div>
</>
);
}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 TabletMockup() {
// 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 tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
// 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
}
.tm-tablet.portrait {
width:300px;height:400px
}
.tm-tablet.landscape {
width:400px;height:300px
}
.tm-tablet.landscape .tm-cam {
top:8px;left:50%;transform:translateX(-50%)
}
.tm-dot.on {
background:#6366f1
}
.tm-head h2 {
font-size:17px;font-weight:800;color:#0f172a
}
.tm-card small {
font-size:10px;color:#94a3b8;font-weight:700
}
.tm-card b {
display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px
}
.tm-spark.s2 {
background:linear-gradient(90deg,#bbf7d0,#22c55e)
}
.tm-spark.s3 {
background:linear-gradient(90deg,#fde68a,#f59e0b)
}
.tm-chart span {
flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85
}
.tm-chart span:nth-child(1) {
height:40%
}
.tm-chart span:nth-child(2) {
height:65%
}
.tm-chart span:nth-child(3) {
height:50%
}
.tm-chart span:nth-child(4) {
height:80%
}
.tm-chart span:nth-child(5) {
height:60%
}
.tm-chart span:nth-child(6) {
height:95%
}
.tm-chart span:nth-child(7) {
height:72%
}
`}</style>
<div className="text-center">
<div className="tm-tablet relative bg-[#0b1220] rounded-[30px] p-4 shadow-[0_30px_60px_-22px_rgba(15,23,42,.5),inset_0_0_0_2px_#1e293b] [transition:width_.5s_cubic-bezier(.65,0,.35,1),height_.5s_cubic-bezier(.65,0,.35,1)] portrait" id="tmTablet">
<span className="tm-cam absolute top-1/2 left-2 w-1.5 h-1.5 rounded-full bg-[#1e293b] [transform:translateY(-50%)]"></span>
<div className="w-full h-full rounded-2xl overflow-hidden bg-[#f8fafc]">
<div className="flex h-full">
<aside className="w-[52px] bg-[#0f172a] flex flex-col items-center gap-3.5 py-4 px-0">
<div className="w-[26px] h-[26px] rounded-lg [background:linear-gradient(135deg,#6366f1,#22d3ee)] mb-1.5"></div>
<span className="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155] on"></span><span className="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span><span className="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span><span className="tm-dot w-[9px] h-[9px] rounded-full bg-[#334155]"></span>
</aside>
<main className="flex-1 p-4 text-left overflow-hidden">
<header className="tm-head flex items-center justify-between mb-3.5"><h2>Dashboard</h2><div className="w-16 h-6 rounded-[99px] bg-[#6366f1]"></div></header>
<div className="grid grid-cols-3 gap-2.5 mb-3.5">
<div className="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Revenue</small><b>$48.2k</b><i className="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)]"></i></div>
<div className="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Users</small><b>9,310</b><i className="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)] s2"></i></div>
<div className="tm-card bg-[#fff] border border-[#e2e8f0] rounded-xl p-2.5"><small>Churn</small><b>1.4%</b><i className="tm-spark block h-[18px] rounded [background:linear-gradient(90deg,#c7d2fe,#6366f1)] s3"></i></div>
</div>
<div className="tm-chart flex items-end gap-2 h-[90px] bg-[#fff] border border-[#e2e8f0] rounded-xl p-3"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" className="mt-[22px] bg-[#0f172a] text-[#fff] border-0 rounded-[9px] py-[9px] px-[18px] text-[12.5px] font-bold cursor-pointer font-[inherit] hover:bg-[#1e293b]" id="tmRotate">Rotate</button>
</div>
</>
);
}<template>
<div class="tm-stage">
<div class="tm-tablet portrait" id="tmTablet">
<span class="tm-cam"></span>
<div class="tm-screen">
<div class="tm-app">
<aside class="tm-side">
<div class="tm-logo"></div>
<span class="tm-dot on"></span><span class="tm-dot"></span><span class="tm-dot"></span><span class="tm-dot"></span>
</aside>
<main class="tm-main">
<header class="tm-head"><h2>Dashboard</h2><div class="tm-pill"></div></header>
<div class="tm-cards">
<div class="tm-card"><small>Revenue</small><b>$48.2k</b><i class="tm-spark"></i></div>
<div class="tm-card"><small>Users</small><b>9,310</b><i class="tm-spark s2"></i></div>
<div class="tm-card"><small>Churn</small><b>1.4%</b><i class="tm-spark s3"></i></div>
</div>
<div class="tm-chart"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" class="tm-rotate" id="tmRotate">Rotate</button>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
let tablet;
onMounted(() => {
tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
});
</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}
.tm-stage{text-align:center}
.tm-tablet{position:relative;background:#0b1220;border-radius:30px;padding:16px;box-shadow:0 30px 60px -22px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b;transition:width .5s cubic-bezier(.65,0,.35,1),height .5s cubic-bezier(.65,0,.35,1)}
.tm-tablet.portrait{width:300px;height:400px}
.tm-tablet.landscape{width:400px;height:300px}
.tm-cam{position:absolute;top:50%;left:8px;width:6px;height:6px;border-radius:50%;background:#1e293b;transform:translateY(-50%)}
.tm-tablet.landscape .tm-cam{top:8px;left:50%;transform:translateX(-50%)}
.tm-screen{width:100%;height:100%;border-radius:16px;overflow:hidden;background:#f8fafc}
.tm-app{display:flex;height:100%}
.tm-side{width:52px;background:#0f172a;display:flex;flex-direction:column;align-items:center;gap:14px;padding:16px 0}
.tm-logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#22d3ee);margin-bottom:6px}
.tm-dot{width:9px;height:9px;border-radius:50%;background:#334155}
.tm-dot.on{background:#6366f1}
.tm-main{flex:1;padding:16px;text-align:left;overflow:hidden}
.tm-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.tm-head h2{font-size:17px;font-weight:800;color:#0f172a}
.tm-pill{width:64px;height:24px;border-radius:99px;background:#6366f1}
.tm-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}
.tm-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:10px}
.tm-card small{font-size:10px;color:#94a3b8;font-weight:700}
.tm-card b{display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px}
.tm-spark{display:block;height:18px;border-radius:4px;background:linear-gradient(90deg,#c7d2fe,#6366f1)}
.tm-spark.s2{background:linear-gradient(90deg,#bbf7d0,#22c55e)}
.tm-spark.s3{background:linear-gradient(90deg,#fde68a,#f59e0b)}
.tm-chart{display:flex;align-items:flex-end;gap:8px;height:90px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:12px}
.tm-chart span{flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85}
.tm-chart span:nth-child(1){height:40%}.tm-chart span:nth-child(2){height:65%}.tm-chart span:nth-child(3){height:50%}.tm-chart span:nth-child(4){height:80%}.tm-chart span:nth-child(5){height:60%}.tm-chart span:nth-child(6){height:95%}.tm-chart span:nth-child(7){height:72%}
.tm-rotate{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 18px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.tm-rotate:hover{background:#1e293b}
</style>// @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-tablet-mockup',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="tm-stage">
<div class="tm-tablet portrait" id="tmTablet">
<span class="tm-cam"></span>
<div class="tm-screen">
<div class="tm-app">
<aside class="tm-side">
<div class="tm-logo"></div>
<span class="tm-dot on"></span><span class="tm-dot"></span><span class="tm-dot"></span><span class="tm-dot"></span>
</aside>
<main class="tm-main">
<header class="tm-head"><h2>Dashboard</h2><div class="tm-pill"></div></header>
<div class="tm-cards">
<div class="tm-card"><small>Revenue</small><b>$48.2k</b><i class="tm-spark"></i></div>
<div class="tm-card"><small>Users</small><b>9,310</b><i class="tm-spark s2"></i></div>
<div class="tm-card"><small>Churn</small><b>1.4%</b><i class="tm-spark s3"></i></div>
</div>
<div class="tm-chart"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
</main>
</div>
</div>
</div>
<button type="button" class="tm-rotate" id="tmRotate">Rotate</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}
.tm-stage{text-align:center}
.tm-tablet{position:relative;background:#0b1220;border-radius:30px;padding:16px;box-shadow:0 30px 60px -22px rgba(15,23,42,.5),inset 0 0 0 2px #1e293b;transition:width .5s cubic-bezier(.65,0,.35,1),height .5s cubic-bezier(.65,0,.35,1)}
.tm-tablet.portrait{width:300px;height:400px}
.tm-tablet.landscape{width:400px;height:300px}
.tm-cam{position:absolute;top:50%;left:8px;width:6px;height:6px;border-radius:50%;background:#1e293b;transform:translateY(-50%)}
.tm-tablet.landscape .tm-cam{top:8px;left:50%;transform:translateX(-50%)}
.tm-screen{width:100%;height:100%;border-radius:16px;overflow:hidden;background:#f8fafc}
.tm-app{display:flex;height:100%}
.tm-side{width:52px;background:#0f172a;display:flex;flex-direction:column;align-items:center;gap:14px;padding:16px 0}
.tm-logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#22d3ee);margin-bottom:6px}
.tm-dot{width:9px;height:9px;border-radius:50%;background:#334155}
.tm-dot.on{background:#6366f1}
.tm-main{flex:1;padding:16px;text-align:left;overflow:hidden}
.tm-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.tm-head h2{font-size:17px;font-weight:800;color:#0f172a}
.tm-pill{width:64px;height:24px;border-radius:99px;background:#6366f1}
.tm-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}
.tm-card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:10px}
.tm-card small{font-size:10px;color:#94a3b8;font-weight:700}
.tm-card b{display:block;font-size:17px;font-weight:800;color:#0f172a;margin:2px 0 8px}
.tm-spark{display:block;height:18px;border-radius:4px;background:linear-gradient(90deg,#c7d2fe,#6366f1)}
.tm-spark.s2{background:linear-gradient(90deg,#bbf7d0,#22c55e)}
.tm-spark.s3{background:linear-gradient(90deg,#fde68a,#f59e0b)}
.tm-chart{display:flex;align-items:flex-end;gap:8px;height:90px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:12px}
.tm-chart span{flex:1;border-radius:4px 4px 0 0;background:#6366f1;opacity:.85}
.tm-chart span:nth-child(1){height:40%}.tm-chart span:nth-child(2){height:65%}.tm-chart span:nth-child(3){height:50%}.tm-chart span:nth-child(4){height:80%}.tm-chart span:nth-child(5){height:60%}.tm-chart span:nth-child(6){height:95%}.tm-chart span:nth-child(7){height:72%}
.tm-rotate{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 18px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.tm-rotate:hover{background:#1e293b}
`]
})
export class TabletMockupComponent implements AfterViewInit {
ngAfterViewInit(): void {
var tablet = document.getElementById('tmTablet');
document.getElementById('tmRotate').addEventListener('click', function () {
var toLandscape = tablet.classList.contains('portrait');
tablet.classList.toggle('portrait', !toLandscape);
tablet.classList.toggle('landscape', toLandscape);
});
}
}