Macbook Scroll

Scroll through the page and see the image come out of the screen.

This Macbook is built with Tailwindcss.
No kidding.

Aceternity Svelte Logo
esc
F1
F2
F3
F4
F5
F6
F7
F8
F8
F10
F11
F12
~ `
! 1
@ 2
# 3
$ 4
% 5
^ 6
& 7
* 8
( 9
) 0
— _
+ =
delete
tab
Q
W
E
R
T
Y
U
I
O
P
{ [
} ]
| \
caps lock
A
S
D
F
G
H
J
K
L
: ;
" '
return
shift
Z
X
C
V
B
N
M
< ,
> .
? /
shift
fn
control
option
command
command
option

Installation

Install Dependencies

npm i svelte-motion clsx tailwind-merge @tabler/icons-svelte

Add util file

src/lib/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
        
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Add the following code in tailwind.config.ts file

tailwind.config.ts
import flattenColorPalette from 'tailwindcss/lib/util/flattenColorPalette';

const config = {
	// ... other properties
	plugins: [
		// ...other plugins
		addVariablesForColors
	]
};

// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
function addVariablesForColors({ addBase, theme }: any) {
	let allColors = flattenColorPalette(theme('colors'));
	let newVars = Object.fromEntries(
		Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
	);

	addBase({
		':root': newVars
	});
}

Copy the source code

src/lib/components/ui/MacbookScroll/MacbookScroll.svelte
<script lang="ts">
	import { Motion, useViewportScroll, useTransform } from 'svelte-motion';
	import Lid from './Lid.svelte';
	import Keypad from './Keypad.svelte';
	import { onMount } from 'svelte';

	export let src: string | undefined = undefined;
	export let showGradient: boolean | undefined = undefined;

	let ref: HTMLDivElement;

	const { scrollYProgress } = useViewportScroll();

	let isMobile = false;

	onMount(() => {
		if (window && window.innerWidth < 768) {
			isMobile = true;
		}
	});

	const scaleX = useTransform(scrollYProgress, [0, 0.3], [1.2, isMobile ? 1 : 1.5]);
	const scaleY = useTransform(scrollYProgress, [0, 0.3], [0.3, isMobile ? 1 : 1.5]);
	const translate = useTransform(scrollYProgress, [0, 1], [-80, 1500]);
	const rotate = useTransform(scrollYProgress, [0.1, 0.12, 0.3], [-28, -28, 0]);
	const textTransform = useTransform(scrollYProgress, [0, 0.3], [0, 100]);
	const textOpacity = useTransform(scrollYProgress, [0, 0.2], [1, 0]);
</script>

<div
	bind:this={ref}
	class="flex min-h-[60vh] min-w-[42rem] flex-shrink-0 scale-[0.35] transform flex-col items-center justify-start py-0 [perspective:800px] sm:scale-50 md:scale-100 md:pb-60 md:pt-28"
>
	<Motion
		let:motion
		style={{
			translateY: textTransform,
			opacity: textOpacity
		}}
	>
		<h2 use:motion class="mb-20 text-center text-3xl font-bold text-neutral-800 dark:text-white">
			<slot name="title">
				<span>
					This Macbook is built with Tailwindcss. <br /> No kidding.
				</span>
			</slot>
		</h2>
	</Motion>
	<Lid {src} {scaleX} {scaleY} {rotate} {translate} />
	<!-- Base Area -->
	<div
		class="relative -z-10 h-[22rem] w-[32rem] overflow-hidden rounded-2xl bg-gray-200 dark:bg-[#272729]"
	>
		<!-- above keyboard bar -->
		<div class="relative h-10 w-full">
			<div class="absolute inset-x-0 mx-auto h-4 w-[80%] bg-[#050505]" />
		</div>
		<div class="relative flex">
			<div class="mx-auto h-full w-[10%] overflow-hidden">
				<div
					class="mt-2 flex h-40 gap-[2px] px-[0.5px]"
					style="background-image: radial-gradient(circle, #08080A 0.5px, transparent 0.5px); background-size: 3px 3px;"
				></div>
			</div>
			<div class="mx-auto h-full w-[80%]">
				<Keypad />
			</div>
			<div class="mx-auto h-full w-[10%] overflow-hidden">
				<div
					class="mt-2 flex h-40 gap-[2px] px-[0.5px]"
					style="background-image: radial-gradient(circle, #08080A 0.5px, transparent 0.5px); background-size: 3px 3px;"
				></div>
			</div>
		</div>
		<div
			class="mx-auto my-1 h-32 w-[40%] rounded-xl"
			style="box-shadow: 0px 0px 1px 1px #00000020 inset;"
		></div>
		<div
			class="absolute inset-x-0 bottom-0 mx-auto h-2 w-20 rounded-tl-3xl rounded-tr-3xl bg-gradient-to-t from-[#272729] to-[#050505]"
		/>
		{#if showGradient}
			<div
				class="absolute inset-x-0 bottom-0 z-50 h-40 w-full bg-gradient-to-t from-white via-white to-transparent dark:from-black dark:via-black"
			></div>
		{/if}

		<div class="absolute bottom-4 left-4">
			<slot name="badge" />
		</div>
	</div>
</div>
src/lib/components/ui/MacbookScroll/Lid.svelte
<script lang="ts">
	import { Motion, MotionValue } from 'svelte-motion';

	export let scaleX: MotionValue<number>;
	export let scaleY: MotionValue<number>;
	export let rotate: MotionValue<number>;
	export let translate: MotionValue<number>;
	export let src: string | undefined = undefined;
</script>

<div class="relative [perspective:800px]">
	<div
		style="transform: perspective(800px) rotateX(-25deg) translateZ(0px); transform-origin: bottom; transform-style: preserve-3d;"
		class="relative h-[12rem] w-[32rem] rounded-2xl bg-[#010101] p-2"
	>
		<div
			style="box-shadow: 0px 2px 0px 2px var(--neutral-900) inset;"
			class="absolute inset-0 flex items-center justify-center rounded-lg bg-[#010101]"
		>
			<span class="text-white">
				<!-- Aceternity Logo -->
				<svg
					width="65"
					height="65"
					viewBox="0 0 65 65"
					fill="none"
					xmlns="http://www.w3.org/2000/svg"
					style="background:black; padding:5px"
					class="h-7 w-7"
					><path
						d="M8 8.05571C8 8.05571 54.9009 18.1782 57.8687 30.062C60.8365 41.9458 9.05432 57.4696 9.05432 57.4696"
						stroke="#EA580C"
						stroke-width="15"
						stroke-miterlimit="3.86874"
						stroke-linecap="round"
					></path></svg
				>
			</span>
		</div>
	</div>
	<Motion
		let:motion
		style={{
			scaleX: scaleX,
			scaleY: scaleY,
			rotateX: rotate,
			translateY: translate,
			transformStyle: 'preserve-3d',
			transformOrigin: 'top'
		}}
	>
		<div use:motion class="absolute inset-0 h-96 w-[32rem] rounded-2xl bg-[#010101] p-2">
			<div class="absolute inset-0 rounded-lg bg-[#272729]" />
			{#if src}
				<img
					{src}
					alt="Aceternity Svelte Logo"
					class="absolute inset-0 h-full w-full rounded-lg object-cover object-left-top"
				/>
			{/if}
		</div>
	</Motion>
</div>
src/lib/components/ui/MacbookScroll/KBtn.svelte
<script lang="ts">
	import { cn } from '@/utils';

	export let className: string | undefined = undefined;
	export let childrenClassName: string | undefined = undefined;
	export let backlit: boolean = true;
</script>

<div class={cn('rounded-[4px] p-[0.5px]', backlit && 'bg-white/[0.2] shadow-xl shadow-white')}>
	<div
		class={cn('flex h-6 w-6 items-center justify-center rounded-[3.5px] bg-[#0A090D]', className)}
		style="box-shadow: 0px -0.5px 2px 0 #0D0D0F inset, -0.5px 0px 2px 0 #0D0D0F inset;"
	>
		<div
			class={cn(
				'flex w-full flex-col items-center justify-center text-[5px] text-neutral-200',
				childrenClassName,
				backlit && 'text-white'
			)}
		>
			<slot />
		</div>
	</div>
</div>
src/lib/components/ui/MacbookScroll/Keypad.svelte
<script>
	import KBtn from './KBtn.svelte';

	import {
		IconBrightnessDown,
		IconBrightnessUp,
		IconCaretRightFilled,
		IconCaretUpFilled,
		IconChevronUp,
		IconMicrophone,
		IconMoon,
		IconPlayerSkipForward,
		IconPlayerTrackNext,
		IconPlayerTrackPrev,
		IconTable,
		IconVolume,
		IconVolume2,
		IconVolume3,
		IconSearch,
		IconWorld,
		IconCommand,
		IconCaretLeftFilled,
		IconCaretDownFilled
	} from '@tabler/icons-svelte';
</script>

<div class="mx-1 h-full rounded-md bg-[#050505] p-1">
	<!-- First Row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn
			className="w-10 items-end justify-start pl-[4px] pb-[2px]"
			childrenClassName="items-start"
		>
			esc
		</KBtn>
		<KBtn>
			<IconBrightnessDown class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F1</span>
		</KBtn>

		<KBtn>
			<IconBrightnessUp class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F2</span>
		</KBtn>
		<KBtn>
			<IconTable class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F3</span>
		</KBtn>
		<KBtn>
			<IconSearch class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F4</span>
		</KBtn>
		<KBtn>
			<IconMicrophone class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F5</span>
		</KBtn>
		<KBtn>
			<IconMoon class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F6</span>
		</KBtn>
		<KBtn>
			<IconPlayerTrackPrev class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F7</span>
		</KBtn>
		<KBtn>
			<IconPlayerSkipForward class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F8</span>
		</KBtn>
		<KBtn>
			<IconPlayerTrackNext class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F8</span>
		</KBtn>
		<KBtn>
			<IconVolume3 class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F10</span>
		</KBtn>
		<KBtn>
			<IconVolume2 class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F11</span>
		</KBtn>
		<KBtn>
			<IconVolume class="h-[6px] w-[6px]" />
			<span class="mt-1 inline-block">F12</span>
		</KBtn>
		<KBtn>
			<div
				class="h-4 w-4 rounded-full bg-gradient-to-b from-neutral-900 from-20% via-black via-50% to-neutral-900 to-95% p-px"
			>
				<div class="h-full w-full rounded-full bg-black" />
			</div>
		</KBtn>
	</div>

	<!-- Second row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn>
			<span class="block">~</span>
			<span class="mt-1 block">`</span>
		</KBtn>

		<KBtn>
			<span class="block">!</span>
			<span class="block">1</span>
		</KBtn>
		<KBtn>
			<span class="block">@</span>
			<span class="block">2</span>
		</KBtn>
		<KBtn>
			<span class="block">#</span>
			<span class="block">3</span>
		</KBtn>
		<KBtn>
			<span class="block">$</span>
			<span class="block">4</span>
		</KBtn>
		<KBtn>
			<span class="block">%</span>
			<span class="block">5</span>
		</KBtn>
		<KBtn>
			<span class="block">^</span>
			<span class="block">6</span>
		</KBtn>
		<KBtn>
			<span class="block">&</span>
			<span class="block">7</span>
		</KBtn>
		<KBtn>
			<span class="block">*</span>
			<span class="block">8</span>
		</KBtn>
		<KBtn>
			<span class="block">(</span>
			<span class="block">9</span>
		</KBtn>
		<KBtn>
			<span class="block">)</span>
			<span class="block">0</span>
		</KBtn>
		<KBtn>
			<span class="block">&mdash;</span>
			<span class="block">_</span>
		</KBtn>
		<KBtn>
			<span class="block">+</span>
			<span class="block"> = </span>
		</KBtn>
		<KBtn className="w-10 items-end justify-end pr-[4px] pb-[2px]" childrenClassName="items-end">
			delete
		</KBtn>
	</div>

	<!-- Third row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn
			className="w-10 items-end justify-start pl-[4px] pb-[2px]"
			childrenClassName="items-start"
		>
			tab
		</KBtn>
		<KBtn>
			<span class="block">Q</span>
		</KBtn>

		<KBtn>
			<span class="block">W</span>
		</KBtn>
		<KBtn>
			<span class="block">E</span>
		</KBtn>
		<KBtn>
			<span class="block">R</span>
		</KBtn>
		<KBtn>
			<span class="block">T</span>
		</KBtn>
		<KBtn>
			<span class="block">Y</span>
		</KBtn>
		<KBtn>
			<span class="block">U</span>
		</KBtn>
		<KBtn>
			<span class="block">I</span>
		</KBtn>
		<KBtn>
			<span class="block">O</span>
		</KBtn>
		<KBtn>
			<span class="block">P</span>
		</KBtn>
		<KBtn>
			<span class="block">{`{`}</span>
			<span class="block">{`[`}</span>
		</KBtn>
		<KBtn>
			<span class="block">{`}`}</span>
			<span class="block">{`]`}</span>
		</KBtn>
		<KBtn>
			<span class="block">{`|`}</span>
			<span class="block">{`\`}</span>
		</KBtn>
	</div>

	<!-- Fourth Row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn
			className="w-[2.8rem] items-end justify-start pl-[4px] pb-[2px]"
			childrenClassName="items-start"
		>
			caps lock
		</KBtn>
		<KBtn>
			<span class="block">A</span>
		</KBtn>

		<KBtn>
			<span class="block">S</span>
		</KBtn>
		<KBtn>
			<span class="block">D</span>
		</KBtn>
		<KBtn>
			<span class="block">F</span>
		</KBtn>
		<KBtn>
			<span class="block">G</span>
		</KBtn>
		<KBtn>
			<span class="block">H</span>
		</KBtn>
		<KBtn>
			<span class="block">J</span>
		</KBtn>
		<KBtn>
			<span class="block">K</span>
		</KBtn>
		<KBtn>
			<span class="block">L</span>
		</KBtn>
		<KBtn>
			<span class="block">{`:`}</span>
			<span class="block">{`;`}</span>
		</KBtn>
		<KBtn>
			<span class="block">{`"`}</span>
			<span class="block">{`'`}</span>
		</KBtn>
		<KBtn
			className="w-[2.85rem] items-end justify-end pr-[4px] pb-[2px]"
			childrenClassName="items-end"
		>
			return
		</KBtn>
	</div>

	<!-- Fifth Row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn
			className="w-[3.65rem] items-end justify-start pl-[4px] pb-[2px]"
			childrenClassName="items-start"
		>
			shift
		</KBtn>
		<KBtn>
			<span class="block">Z</span>
		</KBtn>
		<KBtn>
			<span class="block">X</span>
		</KBtn>
		<KBtn>
			<span class="block">C</span>
		</KBtn>
		<KBtn>
			<span class="block">V</span>
		</KBtn>
		<KBtn>
			<span class="block">B</span>
		</KBtn>
		<KBtn>
			<span class="block">N</span>
		</KBtn>
		<KBtn>
			<span class="block">M</span>
		</KBtn>
		<KBtn>
			<span class="block">{`<`}</span>
			<span class="block">{`,`}</span>
		</KBtn>
		<KBtn>
			<span class="block">{`>`}</span>
			<span class="block">{`.`}</span>
		</KBtn>{' '}
		<KBtn>
			<span class="block">{`?`}</span>
			<span class="block">{`/`}</span>
		</KBtn>
		<KBtn
			className="w-[3.65rem] items-end justify-end pr-[4px] pb-[2px]"
			childrenClassName="items-end"
		>
			shift
		</KBtn>
	</div>

	<!-- Sixth Row -->
	<div class="mb-[2px] flex w-full flex-shrink-0 gap-[2px]">
		<KBtn className="" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-end pr-1">
				<span class="block">fn</span>
			</div>
			<div class="flex w-full justify-start pl-1">
				<IconWorld class="h-[6px] w-[6px]" />
			</div>
		</KBtn>
		<KBtn className="" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-end pr-1">
				<IconChevronUp class="h-[6px] w-[6px]" />
			</div>
			<div class="flex w-full justify-start pl-1">
				<span class="block">control</span>
			</div>
		</KBtn>
		<KBtn className="" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-end pr-1">
				<svg
					fill="none"
					version="1.1"
					id="icon"
					xmlns="http://www.w3.org/2000/svg"
					viewBox="0 0 32 32"
					class="h-[6px] w-[6px]"
				>
					<rect stroke="currentColor" stroke-width={2} x="18" y="5" width="10" height="2" />
					<polygon
						stroke="currentColor"
						stroke-width={2}
						points="10.6,5 4,5 4,7 9.4,7 18.4,27 28,27 28,25 19.6,25 "
					/>
					<rect id="_Transparent_Rectangle_" class="st0" width="32" height="32" stroke="none" />
				</svg>
			</div>
			<div class="flex w-full justify-start pl-1">
				<span class="block">option</span>
			</div>
		</KBtn>
		<KBtn className="w-8" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-end pr-1">
				<IconCommand class="h-[6px] w-[6px]" />
			</div>
			<div class="flex w-full justify-start pl-1">
				<span class="block">command</span>
			</div>
		</KBtn>
		<KBtn className="w-[8.2rem]"></KBtn>
		<KBtn className="w-8" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-start pl-1">
				<IconCommand class="h-[6px] w-[6px]" />
			</div>
			<div class="flex w-full justify-start pl-1">
				<span class="block">command</span>
			</div>
		</KBtn>
		<KBtn className="" childrenClassName="h-full justify-between py-[4px]">
			<div class="flex w-full justify-start pl-1">
				<svg
					fill="none"
					version="1.1"
					id="icon"
					xmlns="http://www.w3.org/2000/svg"
					viewBox="0 0 32 32"
					class="h-[6px] w-[6px]"
				>
					<rect stroke="currentColor" stroke-width={2} x="18" y="5" width="10" height="2" />
					<polygon
						stroke="currentColor"
						stroke-width={2}
						points="10.6,5 4,5 4,7 9.4,7 18.4,27 28,27 28,25 19.6,25 "
					/>
					<rect id="_Transparent_Rectangle_" class="st0" width="32" height="32" stroke="none" />
				</svg>
			</div>
			<div class="flex w-full justify-start pl-1">
				<span class="block">option</span>
			</div>
		</KBtn>
		<div
			class="mt-[2px] flex h-6 w-[4.9rem] flex-col items-center justify-end rounded-[4px] p-[0.5px]"
		>
			<KBtn className="w-6 h-3">
				<IconCaretUpFilled class="h-[6px] w-[6px]" />
			</KBtn>
			<div class="flex">
				<KBtn className="w-6 h-3">
					<IconCaretLeftFilled class="h-[6px] w-[6px]" />
				</KBtn>
				<KBtn className="w-6 h-3">
					<IconCaretDownFilled class="h-[6px] w-[6px]" />
				</KBtn>
				<KBtn className="w-6 h-3">
					<IconCaretRightFilled class="h-[6px] w-[6px]" />
				</KBtn>
			</div>
		</div>
	</div>
</div>
src/lib/components/ui/MacbookScroll/index.ts
import MacbookScroll from './MacbookScroll.svelte';
import MacbookScrollDemo from './MacbookScrollDemo.svelte';
import Keypad from './Keypad.svelte';
import KBtn from './KBtn.svelte';
import Lid from './Lid.svelte';

export { MacbookScroll, MacbookScrollDemo, Keypad, KBtn, Lid };

Props

MacbookScroll
Prop Type Description
src string | undefined Image source for the macbook screen.
showGradient boolean Show/hide the gradient overlay.
title slot (child) A slot to add an element for the title displayed above the Macbook.
badge slot (child) An optional sticker displayed at the bottom left of the Macbook