mirror of
https://github.com/audacity/audacity.github.io.git
synced 2026-06-01 19:51:06 -05:00
feat: snap scrolling with per-section background colors
Replace sticky-scroll layout with full-viewport snap-scrolling sections. Each scroll gesture snaps to the next section like a slide deck. Sections get progressively darker backgrounds (white -> slate-300). Simplified FeatureScene to single description per slide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<section class="bg-slate-100">
|
||||
<section class="bg-slate-100 min-h-screen snap-start snap-always flex items-center">
|
||||
<div class="max-w-screen-lg mx-6 sm:mx-16 xl:mx-auto py-16 md:py-24 flex flex-col items-center text-center gap-6">
|
||||
<h2 class="text-slate-900">Ready to try Audacity 4?</h2>
|
||||
<p class="text-lg text-slate-600 max-w-xl">
|
||||
|
||||
@@ -1,100 +1,28 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { motion, useScroll, useInView, AnimatePresence } from "framer-motion";
|
||||
|
||||
function FeatureScene({ title, descriptions, imageSrc, imageAlt, mirrored = false }) {
|
||||
const sectionRef = useRef(null);
|
||||
const imageRef = useRef(null);
|
||||
const imageInView = useInView(imageRef, { once: true, margin: "-50px" });
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: sectionRef,
|
||||
offset: ["start start", "end end"],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = scrollYProgress.on("change", (v) => {
|
||||
const stepCount = descriptions.length;
|
||||
const index = Math.min(Math.floor(v * stepCount), stepCount - 1);
|
||||
setActiveIndex(index);
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [scrollYProgress, descriptions.length]);
|
||||
|
||||
const sectionHeight = `${descriptions.length * 100 + 50}vh`;
|
||||
import React from "react";
|
||||
|
||||
function FeatureScene({ title, description, imageSrc, imageAlt, mirrored = false, bgClass = "bg-white", textClass = "text-slate-900", bodyClass = "text-slate-600" }) {
|
||||
const imageColumn = (
|
||||
<div className="hidden md:block md:w-[55%]">
|
||||
<div className="sticky top-[50vh] -translate-y-1/2 w-full">
|
||||
<motion.div
|
||||
ref={imageRef}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={imageInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
|
||||
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||
>
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
className="w-full rounded-xl shadow-[0_25px_50px_rgba(0,0,0,0.12)] border border-gray-100"
|
||||
loading="lazy"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2 flex items-center justify-center px-4">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
className="w-full max-w-lg rounded-xl shadow-[0_25px_50px_rgba(0,0,0,0.12)] border border-gray-100"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const textColumn = (
|
||||
<div className="w-full md:w-[40%]">
|
||||
{/* Mobile: stacked layout, no sticky */}
|
||||
<div className="md:hidden flex flex-col gap-8 py-8">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
className="w-full rounded-xl shadow-[0_25px_50px_rgba(0,0,0,0.12)] border border-gray-100"
|
||||
loading="lazy"
|
||||
/>
|
||||
<h2 className="text-slate-900">{title}</h2>
|
||||
{descriptions.map((desc, index) => (
|
||||
<p key={index} className="text-lg text-slate-600 leading-relaxed">{desc}</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Desktop: sticky text that crossfades */}
|
||||
<div className="hidden md:block sticky top-[50vh] -translate-y-1/2">
|
||||
<div className="relative w-full">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeIndex}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<h2 className="text-slate-900 mb-4">{title}</h2>
|
||||
<p className="text-lg text-slate-600 leading-relaxed">
|
||||
{descriptions[activeIndex]}
|
||||
</p>
|
||||
{/* Progress dots */}
|
||||
<div className="flex gap-2 mt-8">
|
||||
{descriptions.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-2 h-2 rounded-full transition-colors duration-300 ${
|
||||
index === activeIndex ? "bg-blue-700" : "bg-slate-300"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2 flex items-center px-4 md:px-8">
|
||||
<div>
|
||||
<h2 className={`${textClass} mb-4`}>{title}</h2>
|
||||
<p className={`text-lg leading-relaxed ${bodyClass}`}>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section ref={sectionRef} className="bg-white">
|
||||
<div className={`max-w-screen-lg mx-6 sm:mx-16 xl:mx-auto py-12 md:py-[50vh] flex flex-col md:flex-row ${mirrored ? "md:flex-row-reverse" : ""} gap-8 md:gap-12`} style={{ minHeight: sectionHeight }}>
|
||||
<section className={`h-screen snap-start snap-always ${bgClass}`}>
|
||||
<div className={`h-full max-w-screen-lg mx-auto px-6 sm:px-16 flex flex-col md:flex-row ${mirrored ? "md:flex-row-reverse" : ""} items-center gap-8 md:gap-12`}>
|
||||
{imageColumn}
|
||||
{textColumn}
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import PlaceholderUI from "../../assets/img/audacity4/placeholder-ui.svg";
|
||||
---
|
||||
|
||||
<section id="main" class="bg-gradient-to-b from-slate-50 to-white min-h-screen">
|
||||
<div class="max-w-screen-lg mx-6 sm:mx-16 xl:mx-auto py-16 md:py-24 flex flex-col items-center justify-center text-center gap-8 md:gap-12 min-h-screen">
|
||||
<section id="main" class="bg-gradient-to-b from-slate-50 to-white h-screen snap-start snap-always">
|
||||
<div class="max-w-screen-lg mx-6 sm:mx-16 xl:mx-auto py-16 md:py-24 flex flex-col items-center justify-center text-center gap-8 md:gap-12 h-full">
|
||||
<div class="flex flex-col gap-4 max-w-2xl">
|
||||
<h1 class="text-slate-900 leading-tight">
|
||||
Meet Audacity 4
|
||||
|
||||
@@ -20,64 +20,68 @@ import PlaceholderSplit from "../assets/img/audacity4/placeholder-split.svg";
|
||||
<FeatureScene
|
||||
client:load
|
||||
title="A completely redesigned interface"
|
||||
description="Audacity 4 introduces a ground-up redesign of the entire interface. Every toolbar, panel, and menu has been rethought for clarity and speed."
|
||||
imageSrc={PlaceholderUI.src}
|
||||
imageAlt="The new Audacity 4 interface with redesigned toolbar and track layout"
|
||||
descriptions={[
|
||||
"Audacity 4 introduces a ground-up redesign of the entire interface. Every toolbar, panel, and menu has been rethought for clarity and speed.",
|
||||
"The new track panel gives you a cleaner overview of your project, with streamlined controls that stay out of the way until you need them.",
|
||||
"A modernized toolbar puts your most-used tools front and center, with logical groupings that match the way you actually work.",
|
||||
"The result is an interface that feels familiar to long-time users but dramatically more efficient for everyone."
|
||||
]}
|
||||
mirrored={false}
|
||||
bgClass="bg-white"
|
||||
textClass="text-slate-900"
|
||||
bodyClass="text-slate-600"
|
||||
/>
|
||||
|
||||
<FeatureScene
|
||||
client:load
|
||||
title="Select multiple clips at once"
|
||||
description="For the first time in Audacity, you can select and manipulate multiple clips across tracks in a single action. Click, shift-click, or drag to select exactly what you need."
|
||||
imageSrc={PlaceholderClips.src}
|
||||
imageAlt="Multiple audio clips selected simultaneously across different tracks"
|
||||
descriptions={[
|
||||
"For the first time in Audacity, you can select and manipulate multiple clips across tracks in a single action. Click, shift-click, or drag to select exactly what you need.",
|
||||
"Move, delete, copy, or apply effects to your entire selection at once. No more tedious one-clip-at-a-time editing."
|
||||
]}
|
||||
mirrored={true}
|
||||
bgClass="bg-slate-50"
|
||||
textClass="text-slate-900"
|
||||
bodyClass="text-slate-600"
|
||||
/>
|
||||
|
||||
<FeatureScene
|
||||
client:load
|
||||
title="Stretch clips with a new algorithm"
|
||||
description="Grab the edge of any clip and drag to stretch or compress it in time. The new stretching algorithm preserves audio quality even at extreme ratios."
|
||||
imageSrc={PlaceholderStretch.src}
|
||||
imageAlt="An audio clip being stretched using drag handles on its edges"
|
||||
descriptions={[
|
||||
"Grab the edge of any clip and drag to stretch or compress it in time. The new stretching algorithm preserves audio quality even at extreme ratios.",
|
||||
"Whether you're time-aligning dialogue, adjusting music loops, or syncing sound effects, stretch handles make it intuitive and fast."
|
||||
]}
|
||||
mirrored={false}
|
||||
bgClass="bg-slate-100"
|
||||
textClass="text-slate-900"
|
||||
bodyClass="text-slate-600"
|
||||
/>
|
||||
|
||||
<FeatureScene
|
||||
client:load
|
||||
title="Group clips together"
|
||||
description="Select clips across tracks and group them into a single unit. Grouped clips move, copy, and delete together — keeping your carefully-arranged edits intact."
|
||||
imageSrc={PlaceholderGroups.src}
|
||||
imageAlt="Multiple clips grouped together with a visual bounding outline"
|
||||
descriptions={[
|
||||
"Select clips across tracks and group them into a single unit. Grouped clips move, copy, and delete together — keeping your carefully-arranged edits intact.",
|
||||
"Groups are non-destructive and can be ungrouped at any time. Perfect for managing complex multi-track projects."
|
||||
]}
|
||||
mirrored={true}
|
||||
bgClass="bg-slate-200"
|
||||
textClass="text-slate-900"
|
||||
bodyClass="text-slate-700"
|
||||
/>
|
||||
|
||||
<FeatureScene
|
||||
client:load
|
||||
title="The new Split tool"
|
||||
description="The dedicated Split tool gives you a precise way to divide clips exactly where you need them. Combined with multi-clip selection and clip groups, it completes a powerful new editing workflow."
|
||||
imageSrc={PlaceholderSplit.src}
|
||||
imageAlt="The split tool cursor positioned over an audio clip, ready to split it"
|
||||
descriptions={[
|
||||
"The dedicated Split tool gives you a precise way to divide clips exactly where you need them. Click anywhere on a clip to split it at that point.",
|
||||
"Combined with multi-clip selection and clip groups, the Split tool completes a powerful new editing workflow that puts you in full control of your audio."
|
||||
]}
|
||||
mirrored={false}
|
||||
bgClass="bg-slate-300"
|
||||
textClass="text-slate-900"
|
||||
bodyClass="text-slate-700"
|
||||
/>
|
||||
|
||||
<CTAFooter />
|
||||
</BaseLayout>
|
||||
|
||||
<style is:global>
|
||||
html {
|
||||
scroll-snap-type: y mandatory;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user