
The mask that opens
Transitions, Scroll, SVG
NewFree
An amber poster: two lines on the beat, a dark frame between them and a violin coming out of it, cut only at the bottom. The first scroll widens the frame until it becomes the night of the whole page, and the violin flies to its place and tilts.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The frame that becomes the page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<section class="hero" data-hero>
<header class="top" data-bar>
<p class="brand">Lanterna Philharmonic</p>
<p class="top__line">Season tickets</p>
</header>
<!-- for the keyboard (and the catalogue's autopilot): opens the night, or goes back to the poster -->
<button class="toggle" type="button" data-toggle data-autoclic>
<span class="toggle__text" data-text="Open the night">Open the night</span>
</button>
<!-- the stage: the poster's two lines, the frame's place between them, and over them the portal -->
<div class="stage" data-stage>
<h1 class="title">
<span class="line line--top" data-line><span class="line__in">The <br class="line__break">city’s</span></span>
<span class="line line--bottom" data-line><span class="line__in">New <br class="line__break">sound</span></span>
</h1>
<span class="slot" data-slot aria-hidden="true"></span>
<!-- everything in the portal is cut along the frame's lower edge, and nowhere else -->
<div class="portal" data-portal>
<div class="rect" data-rect></div>
<p class="welcome fit" data-welcome aria-hidden="true">
<span class="fit__box"><span class="fit__display" data-welcome-text>Opening <br class="line__break">night</span></span>
<span class="fit__ref">Opening <br class="line__break">night</span>
</p>
<!-- the violin's box is its place in the night; in the poster a transform takes it into the frame -->
<div class="violin" data-violin>
<div class="violin__tilt" data-tilt>
<div class="violin__drift" data-drift>
<svg class="violin__art" viewBox="0 0 600 1100" aria-hidden="true">
<defs>
<linearGradient id="varnish" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f3a04a" />
<stop offset="0.45" stop-color="#d6661f" />
<stop offset="1" stop-color="#9a3d0e" />
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#ffcf8c" stop-opacity="0.8" />
<stop offset="1" stop-color="#ffcf8c" stop-opacity="0" />
</radialGradient>
<radialGradient id="shade" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#5a1f04" stop-opacity="0.55" />
<stop offset="1" stop-color="#5a1f04" stop-opacity="0" />
</radialGradient>
<linearGradient id="head" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#f09b48" />
<stop offset="1" stop-color="#a8440f" />
</linearGradient>
<clipPath id="outline"><path d="M300 420 C372 419 440 446 441 520 C442 568 424 592 412 604 C420 610 430 616 434 624 C414 624 396 636 391 656 C387 672 390 692 398 704 C408 712 424 714 436 716 C432 724 426 730 420 736 C458 760 478 808 478 872 C478 962 408 1018 300 1020 C192 1018 122 962 122 872 C122 808 142 760 180 736 C174 730 168 724 164 716 C176 714 192 712 202 704 C210 692 213 672 209 656 C204 636 186 624 166 624 C170 616 180 610 188 604 C176 592 158 568 159 520 C160 446 228 419 300 420 Z" /></clipPath>
</defs>
<!-- the bow, behind the violin: stick, hair, tip, frog and button -->
<g transform="translate(46 70) rotate(63.03)">
<path d="M16 15 L1000 22 L1000 28 L22 21 Z" fill="#f5ecd8" />
<path d="M0 -3 L1000 -6 L1000 6 L0 3 Z" fill="#7a3c16" />
<path d="M-7 -6 C -3 -13 13 -13 21 -4 L 24 27 L 11 27 C 4 20 -5 8 -7 -6 Z" fill="#f5ecd8" />
<path d="M994 -5 L1070 -5 L1080 26 L1060 44 L1002 44 Z" fill="#1c1512" />
<circle cx="1038" cy="19" r="7" fill="#dfe9f5" />
<path d="M1080 -6 L1128 -6 L1128 6 L1080 6 Z" fill="#1c1512" />
<path d="M1092 -7 L1102 -7 L1102 7 L1092 7 Z" fill="#d9d2c4" />
</g>
<!-- pegs, pegbox and scroll -->
<g fill="#1c1512">
<rect x="222" y="160" width="60" height="10" rx="5" /><ellipse cx="220" cy="165" rx="15" ry="11" />
<rect x="222" y="214" width="60" height="10" rx="5" /><ellipse cx="220" cy="219" rx="15" ry="11" />
<rect x="318" y="186" width="60" height="10" rx="5" /><ellipse cx="380" cy="191" rx="15" ry="11" />
<rect x="318" y="240" width="60" height="10" rx="5" /><ellipse cx="380" cy="245" rx="15" ry="11" />
</g>
<path d="M275 130 L325 130 L322 264 L278 264 Z" fill="url(#head)" />
<path d="M287 142 L313 142 L311 258 L289 258 Z" fill="#3b1606" />
<circle cx="300" cy="96" r="38" fill="url(#head)" />
<circle cx="300" cy="96" r="38" fill="none" stroke="#7a2f0b" stroke-width="3" />
<path d="M300.0 93.0 L300.5 92.7 L301.2 92.4 L301.9 92.3 L302.7 92.4 L303.6 92.6 L304.4 93.0 L305.2 93.6 L305.8 94.4 L306.4 95.3 L306.8 96.4 L307.1 97.5 L307.1 98.8 L306.9 100.1 L306.4 101.4 L305.7 102.7 L304.7 103.8 L303.5 104.8 L302.2 105.7 L300.6 106.3 L298.9 106.6 L297.1 106.7 L295.2 106.4 L293.4 105.8 L291.6 104.9 L290.0 103.7 L288.6 102.1 L287.4 100.3 L286.5 98.3 L285.9 96.1 L285.7 93.7 L285.9 91.3 L286.5 88.9 L287.5 86.6 L288.9 84.4 L290.7 82.5 L292.8 80.8 L295.3 79.5 L298.0 78.5 L300.8 78.1 L303.8 78.1 L306.8 78.6 L309.7 79.6 L312.5 81.1 L315.0 83.0 L317.3 85.4 L319.1 88.2 L320.5 91.3 L321.4 94.7 L321.7 98.2 L321.4 101.7 L320.6 105.3 L319.1 108.7 L317.1 111.9 L314.5 114.7 L311.5 117.2 L308.0 119.1 L304.3 120.5 L300.3 121.2 L296.1 121.3 L292.0 120.7" fill="none" stroke="#6e2a08" stroke-width="5" stroke-linecap="round" />
<path d="M276 72 C 284 62 296 58 306 60" fill="none" stroke="#ffd29a" stroke-width="3" stroke-linecap="round" opacity="0.7" />
<!-- neck under the fingerboard -->
<path d="M280 258 L320 258 L324 440 L276 440 Z" fill="#b24b14" />
<!-- body: a darker rim, the top, grain, light and shade, the purfling -->
<path d="M300 420 C372 419 440 446 441 520 C442 568 424 592 412 604 C420 610 430 616 434 624 C414 624 396 636 391 656 C387 672 390 692 398 704 C408 712 424 714 436 716 C432 724 426 730 420 736 C458 760 478 808 478 872 C478 962 408 1018 300 1020 C192 1018 122 962 122 872 C122 808 142 760 180 736 C174 730 168 724 164 716 C176 714 192 712 202 704 C210 692 213 672 209 656 C204 636 186 624 166 624 C170 616 180 610 188 604 C176 592 158 568 159 520 C160 446 228 419 300 420 Z" fill="#7a2f0b" stroke="#6e2a08" stroke-width="14" stroke-linejoin="round" />
<path d="M300 420 C372 419 440 446 441 520 C442 568 424 592 412 604 C420 610 430 616 434 624 C414 624 396 636 391 656 C387 672 390 692 398 704 C408 712 424 714 436 716 C432 724 426 730 420 736 C458 760 478 808 478 872 C478 962 408 1018 300 1020 C192 1018 122 962 122 872 C122 808 142 760 180 736 C174 730 168 724 164 716 C176 714 192 712 202 704 C210 692 213 672 209 656 C204 636 186 624 166 624 C170 616 180 610 188 604 C176 592 158 568 159 520 C160 446 228 419 300 420 Z" fill="url(#varnish)" />
<g clip-path="url(#outline)">
<path d="M140 400 C140.0 600 140.0 800 140.0 1040 M156 400 C161.9499888627148 600 150.0500111372852 800 158.9749944313574 1040 M172 400 C170.46675338783902 600 173.53324661216098 800 171.2333766939195 1040 M188 400 C182.44511190603362 600 193.55488809396638 800 185.2225559530168 1040 M204 400 C206.96468010683165 600 201.03531989316835 800 205.4823400534158 1040 M220 400 C224.79092267574094 600 215.20907732425906 800 222.39546133787047 1040 M236 400 C231.80075187443876 600 240.19924812556124 800 233.90037593721937 1040 M252 400 C248.2911773265778 600 255.7088226734222 800 250.1455886632889 1040 M268 400 C273.15497088913895 600 262.84502911086105 800 270.5774854445695 1040 M284 400 C286.3804434387837 600 281.6195565612163 800 285.19022171939184 1040 M300 400 C294.2316150487227 600 305.7683849512773 800 297.11580752436134 1040 M316 400 C315.1060058451148 600 316.8939941548852 800 315.5530029225574 1040 M332 400 C337.998757400856 600 326.001242599144 800 334.999378700428 1040 M348 400 C347.34818042745553 600 348.65181957254447 800 347.67409021372777 1040 M364 400 C358.1692093255368 600 369.8307906744632 800 361.0846046627684 1040 M380 400 C382.15435012413303 600 377.84564987586697 800 381.0771750620665 1040 M396 400 C401.27563836990436 600 390.72436163009564 800 398.63781918495215 1040 M412 400 C408.48617596015424 600 415.51382403984576 800 410.2430879800771 1040 M428 400 C423.6298353930104 600 432.3701646069896 800 425.81491769650523 1040 M444 400 C448.6399673373973 600 439.3600326626027 800 446.3199836686987 1040 M460 400 C463.17449611672015 600 456.82550388327985 800 461.58724805836005 1040 " fill="none" stroke="#ffd29a" stroke-width="1.6" opacity="0.18" />
<ellipse cx="228" cy="512" rx="130" ry="120" fill="url(#glow)" />
<ellipse cx="220" cy="860" rx="160" ry="150" fill="url(#glow)" />
<ellipse cx="470" cy="800" rx="90" ry="260" fill="url(#shade)" />
<ellipse cx="440" cy="520" rx="60" ry="110" fill="url(#shade)" />
</g>
<path d="M300 420 C372 419 440 446 441 520 C442 568 424 592 412 604 C420 610 430 616 434 624 C414 624 396 636 391 656 C387 672 390 692 398 704 C408 712 424 714 436 716 C432 724 426 730 420 736 C458 760 478 808 478 872 C478 962 408 1018 300 1020 C192 1018 122 962 122 872 C122 808 142 760 180 736 C174 730 168 724 164 716 C176 714 192 712 202 704 C210 692 213 672 209 656 C204 636 186 624 166 624 C170 616 180 610 188 604 C176 592 158 568 159 520 C160 446 228 419 300 420 Z" fill="none" stroke="#4a1a05" stroke-width="2.5" transform="translate(300 720) scale(0.955) translate(-300 -720)" />
<!-- f-holes, with their nicks -->
<g fill="none" stroke="#2b1206" stroke-width="6.5" stroke-linecap="round">
<path d="M227 650 C 247 670 245 702 233 730 C 221 758 215 790 233 816" />
<path d="M373 650 C 353 670 355 702 367 730 C 379 758 385 790 367 816" />
<path d="M226 734 L 242 740 M 374 734 L 358 740" stroke-width="4" />
</g>
<g fill="#2b1206">
<circle cx="225" cy="648" r="8.5" /><circle cx="235" cy="818" r="9.5" />
<circle cx="375" cy="648" r="8.5" /><circle cx="365" cy="818" r="9.5" />
</g>
<!-- fingerboard and nut -->
<path d="M278 256 L322 256 L336 660 C 328 670 272 670 264 660 Z" fill="#1c1512" />
<path d="M287 266 L296 266 L291 650 L280 650 Z" fill="#3a2d27" opacity="0.8" />
<rect x="276" y="252" width="48" height="8" rx="3" fill="#efe3c8" />
<!-- chinrest, tailpiece, saddle, end button -->
<path d="M236 990 C 198 1002 166 986 168 962 C 170 942 200 934 236 944 C 268 952 274 980 236 990 Z" fill="#1c1512" />
<path d="M200 956 C 212 948 234 948 250 956" fill="none" stroke="#4a3b33" stroke-width="4" stroke-linecap="round" />
<path d="M264 872 L336 872 C 336 930 326 986 314 1004 L286 1004 C 274 986 264 930 264 872 Z" fill="#1c1512" />
<path d="M272 880 L284 880 L292 994 L286 994 Z" fill="#3a2d27" opacity="0.8" />
<circle cx="324" cy="888" r="6" fill="#cfc8ba" />
<rect x="284" y="1010" width="32" height="8" rx="2" fill="#1c1512" />
<circle cx="300" cy="1027" r="9" fill="#1c1512" />
<!-- bridge -->
<path d="M246 782 L354 782 L348 770 C 342 756 332 748 300 746 C 268 748 258 756 252 770 Z" fill="#ecc98f" />
<path d="M284 767 C 289 760 311 760 316 767 C 310 773 290 773 284 767 Z" fill="#b98e55" />
<path d="M262 776 C 268 770 276 770 280 776 M 320 776 C 324 770 332 770 338 776" fill="none" stroke="#b98e55" stroke-width="3" />
<rect x="242" y="780" width="116" height="6" rx="2" fill="#d7ad6c" />
<!-- strings -->
<g stroke="#f3ead8" stroke-width="2.2" data-strings>
<line x1="291" y1="256" x2="282" y2="874" />
<line x1="297" y1="256" x2="294" y2="874" />
<line x1="303" y1="256" x2="306" y2="874" />
<line x1="309" y1="256" x2="318" y2="874" />
</g>
</svg>
</div>
</div>
</div>
</div>
<div class="fade" data-fade></div>
<div class="intro" data-intro>
<h2 class="visually-hidden">Opening night</h2>
<p>The Lanterna Philharmonic opens its first season in the new hall by the harbour, with the doors wide open and every seat under twenty euros.</p>
<p>Scroll on for the programme, or go back to the top and scroll up to see the poster again.</p>
</div>
<p class="hint" data-hint>
Scroll to open
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v15M6 13l6 6 6-6" /></svg>
</p>
</div>
</section>
<section class="programme">
<h2 class="programme__title">This season</h2>
<ul class="programme__list">
<li><span class="programme__date">Fri 2 October</span><span class="programme__name">Opening night</span><span class="programme__place">Harbour Hall</span></li>
<li><span class="programme__date">Fri 16 October</span><span class="programme__name">Ravel by the water</span><span class="programme__place">Harbour Hall</span></li>
<li><span class="programme__date">Sun 25 October</span><span class="programme__name">Brass on the morning ferries</span><span class="programme__place">Pier Four</span></li>
<li><span class="programme__date">Fri 6 November</span><span class="programme__name">Mahler in the shipyard</span><span class="programme__place">Old Shipyard</span></li>
</ul>
<p class="programme__end">Doors open at seven.</p>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body>
</html>Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.
45 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The frame that becomes the page” from Animorio and fit it into my siteThe two lines and the frame’s place are a three-row grid inside a container, so the heading also sizes itself on the height (36cqh). The lines appear at 0.63 and 1.26 seconds with a small scale knock; at 1.89 the frame opens from scaleX 0 over 1.76 seconds expo.out, and the violin rises from 55% of its height lower down over 1.76 quint.out.
Frame, violin and the night’s title sit in a layer as big as the stage, between the two lines: the top line goes under it, the bottom one over it. The layer is cut with clip-path inset(0 0 X 0), where X is the distance from the frame’s lower edge to the bottom: the violin may come out above and at the sides, never below the frame.
The frame grows from its centre until it covers the stage plus 8 px (scale = twice the distance to the farthest edge, divided by its size) over 1.2 seconds quint.inOut, and the cut moves down at the same pace as its lower edge. The violin, whose CSS already gives it its place and size in the night, goes back to scale 1 from the poster’s scale and tilts by 20° over 1.4 seconds; that is why it stays sharp. At 0.77 «Opening night», fitted to the width with CSS alone, comes up from 180 px lower and scale 0.8 over 1 second expo.out.
While the poster is up the html has overflow: hidden and the wheel, the finger and the keys don’t scroll (non-passive listeners with preventDefault): the first gesture down starts the flight. Afterwards the page scrolls normally and a scroll-linked timeline lifts the text by 100 px and the violin by 10° more. At the top, a gesture up rewinds the flight and holds the page still again.
The night’s title and the text have a timeline of their own, which starts during the flight and on the way back runs 2.2 times faster: had they stayed in the flight, rewinding it from the end would have shown for almost a second nothing but the tail of the words, nearly still.
| Name | File | Default | What it changes |
|---|---|---|---|
BEAT, OPEN | script.js | 0.63, 1.76 | The seconds between one line and the next (and before the frame), and how long frame and violin take to open and rise. |
SIZE, SINK | script.js | 0.56, 0.75 | How wide the violin is in the poster, compared with the frame, and how far its foot goes below the lower edge, compared with the frame’s height. |
GROW, FLY, TILT | script.js | 1.2, 1.4, 20 | The seconds of the frame covering the page and of the violin flying, and the degrees it tilts. |
WELCOME_AT | script.js | 0.77 | When the night’s title comes up, during the flight. |
SWAY, RING | script.js | 1.5, 3 | The degrees the violin always sways by, and how far the strings swing when it lands. |
For a first page that shows itself as a poster and then opens: a season launch, a new director, a product coming out of a box, an event with one strong image.
The lines wrap and sit above and below the frame; the same gesture, with a finger, opens the night. In the night the violin sits in the middle and the text at the bottom, over a dark fade.
The poster is already drawn and the violin is still; the first gesture cuts straight to the night, and at the top a gesture up goes back to the poster the same way.
GSAP 3.15.0, ScrollTrigger (GSAP) 3.15.0, Big Shoulders (font variabile) 5.3.0, Newsreader (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP + ScrollTrigger 45 KB.
Tested on 29 September 2026
Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.