What it is
A resort site, complete: a full-height hero with its booking bar, five intro tiles one of which is a video, four numbered stays, a carousel of four rooms, the reviews, five questions and a call-to-action block behind two drifting walls of photos. One page, one HTML file.
The design follows that of a Webflow template. Everything else has been rebuilt: the original came to 345 kB of minified CSS, jQuery, three Webflow bundles, two proprietary animation engines — the old one and the new one, both loaded — GSAP with ScrollTrigger and SplitText, a smooth-scrolling library, and five blocks carrying an inline style="opacity:0" that a strict security policy blocks, which is enough to leave the page blank.
The 27 photographs were generated for this template, and the video is computed locally from one of them. Nothing here depends on a licence that isn't ours — except the fonts, which are under a free licence.
That's the whole point: this template isn't here to be admired, it's here to be read, broken and rebuilt.
What you change first
The colours — one line
Right at the top of assets/css/style.css:
--ink-rgb: 11 43 51; /* #0b2b33 */
It tints everything: the text, the buttons, the footer, and every grey on the page — which are only that ink at a lower opacity. Changing it is enough to retint the whole site. The original was a cold midnight blue, #0b1f33; Motuvai shifts it towards the blue-green of the lagoon.
The accent exists in three shades that belong together:
--ink for the solid one (14.9:1 on white), --ink-strong
for hover (18.8:1) and --ink-light for the accent laid over a photograph (7.9:1 on the ink). Changing one means changing all three, otherwise the pressed state stops reading as the same colour. The grey of secondary text holds at 4,7:1 — just above the AA threshold: lightening it would take it below.
The name
Search for “Motuvai” in index.html: it appears in the title, the navigation bar, the footer, the giant word at the bottom of the page and two questions. No CSS class carries the brand name, and the logo is text: a find-and-replace can't break anything, and there's no image to remake.
The sizes
The type scale is declared once at the top of the stylesheet, then redeclared at 767 and 479 px. So a heading never gets its own media query: you change --h2-size and every h2 on the page follows.
The fourteen effects, and how they're built
The original carries two animation engines at once. Both were pulled out of the bundle and read: 296 events for 39 action lists on the old engine's side — it creates one per element, not per effect — and 8 interactions on the new one's. After deduplication and cross-checking with what the home page actually carries, fourteen distinct effects remain.
| Effect | Original settings | How |
|---|---|---|
| Crisp fade-in (7 blocks) | opacity 0 → 1 in 1,000 ms ease, blur 5 → 0 px over 1,000 ms delayed by 200 ms | [data-reveal] and an IntersectionObserver; threshold moved to 15% up from the bottom of the screen |
| The same, slower | 1,400 ms for the hero | a single token redeclared, --reveal-time |
| Photo parallax (8) | y −15% → 0% over the whole crossing of the screen | the script writes a progress from 0 to 1 into --p, and the CSS turns it into the translation. The photo is a fifth taller than its frame: it's that surplus that travels |
| Stays straightening up (3 of 4) | y −100 px → 0 and rotation −3° → 0°, over the first 30 per cent of the crossing | the same --p, capped at 0.3 |
| Zoom on hover | scale 1 → 1.1 in 500 ms outCirc | the same, same curve |
| Neighbouring links blurred | hovering a footer link: the others go to 7 px of blur in 500 ms | two CSS rules, zero JavaScript |
| Neighbours dimmed | hovering a navigation link: the others fall to 32% in 300 ms | the same two rules, applied to the navigation bar |
| Button label that rolls | text split into characters, y → −1.5em in 635 ms, stagger spread over 100 ms | the script splits and duplicates the text, the CSS transitions each letter by its index. Without the script, the button is still a button |
| Button background that sinks | scale → 0.95 on hover | same |
| Drifting walls of photos | y 0 → −50% and −50% → 0 in 25 s, linear, looping, columns tilted by 31° | two @keyframes on two identical stacked bands: sliding by exactly half puts the second where the first began, so there's no visible seam |
| Rating counter | blur 6 → 0 px in 2.25 s power2.out, value 0 → 9.7 in 2 s | an animation loop and the curve 1 − (1 − t)² |
| Questions accordion | height 0 ↔ auto in 400 / 500 ms outCirc, the “+” turns | <details> native; the height is animated by the script, because a closed <details> is hidden by the browser and no keyframe replays inside it |
| Room carousel | Webflow slider, the centre card sharp, its neighbours shrunk and blurred | scroll-snap native: it slides, swipes and can be reached from the keyboard without JavaScript. The script only adds the arrows, the position announcement and the blur on the neighbours |
| Menu and submenu | opening in 200 / 500 ms, height 0 ↔ 100%, chevron 0 ↔ 180° | the same, plus two hover delays to let the cursor cross the gap, closing on Escape and on clicking a link |
Three effects declared in the registry play nowhere on this page — which is exactly what deduplication brings to light, so they haven't been carried over: a letter-by-letter text stagger no element carries the attribute for, a parallax belonging to another page of the template, and the editor's promotional banner.
What you should know
- No external requests. Checked by listing the requests on load: every one is local. The original loaded jQuery, three Webflow bundles, three GSAP files, a scrolling library and two fonts from four domains.
-
Scroll reveals can't hide the content.
The hidden state only exists once the JavaScript has run. Checked by switching the scripts off: all 19 headings stay visible, the menu is open, the five questions open and the carousel can be dragged. The original, meanwhile, set
opacity: 0as a style attribute on five blocks — which a strict security policy blocks, and the page goes blank. - Zero inline styles, zero inline scripts. Checked by serving the folder with the site's policy in front of the page, not assumed: zero violations, zero JavaScript errors, at all three widths.
- The video only loads on click. The card shows its fallback image and only fetches the 396 kB if you press play. The original downloaded two 3840 × 2160 files as soon as the page opened, to show them in a 448 × 360 card.
- The trigger sits 15% up from the bottom of the screen, measured against the screen and not against the element: a threshold as a percentage of the element would leave any block taller than the screen invisible for ever. A safety net reveals whatever is left when you reach the bottom of the page.
-
Accessibility: skip link, menu and submenu states announced, native questions, carousel position announced through
role="status", a single visible focus style, decorations hidden from screen readers, animations and delays switched off if the system asks. - Responsive checked at 390, 1440 and 2560 px, on load and after scrolling all the way down, without a pixel of horizontal overflow.
What isn't perfect
Better said here than left for you to find out.
- The call-to-action block's background is no longer a video. The original serves a 1,280 × 720 shot of water there, stretched to 1,408 × 560 and almost entirely washed out behind a white scrim. Here it's a still photograph, desaturated, given a slow drift in CSS: same impression, no video decoder, and a megabyte less.
- The card's video is a computed movement. A slow advance down the avenue of coconut palms, composed from a generated image then played back and forth so the loop has no seam. Compared after re-encoding: WebM came to 808 kB against 396 kB for mp4, so mp4 is what's served, on its own.
- The smooth scrolling hasn't been carried over. The original loads a library that hijacks everyone's wheel, including that of people who don't want it. Native scrolling is kept, with smoothing on anchors only.
- The stays are 9% shorter at 479 px. The four stacked blocks are more compact than the original's, whose copy takes one line more at that width. Nothing overlaps. The footer, meanwhile, is 6% taller at 767 and 479 px: its columns hold six entries where the original lines up seven.
-
The carousel's arrows don't loop. Once at the last card, the button is disabled rather than going back to the start: a
scroll-snaprail can't loop without duplicating its cards, and a greyed-out arrow tells the truth. - The inner pages don't exist. The original is a multi-page site — blog, gallery, room pages, legal notices: about twenty pages. All its links are brought back to anchors on the single page, rather than leaving 404s.
- The forms have no server. The booking form and the newsletter validate with the browser's own rules then show the confirmation message the original displayed after a successful send. A static template has no booking engine.
- One flaw in the original fixed. The first testimonial described a family stay under the label “Adventure Seeker”, and the second a quiet stay under “Family Traveler”. The roles have been put back opposite the right quotations.
-
The footer's blur and the glass panes rely on
backdrop-filter. A browser that ignores it shows a sharp background: the text stays readable, the effect disappears.
Take it, open it, break it.
Motuvai is free. The full library — the other templates and the components — comes with the subscription, alongside the course.
Immediate access · No commitment · Cancel in one click