用 gsap 重新创建滑块并做出反应

Recreating slider with gsap and react

我正在尝试在此处使用 React 和 gsap 重新创建此代码笔,我已经尝试重新创建它几个小时了,甚至不知道从哪里开始。我不想在那个 codepen 中以相同的方式创建这些部分,而是从一组对象中形成细节,这意味着只创建一次组件使其干燥。我对反应还很陌生,我想知道如何在反应中做类似的事情,从 vanillajs 的角度来看。我已经知道发生了什么,我也在反应中像这样创建了它,但我想要一种情况,我的代码最少,并且对象数组形成一个组件,使其更具动态性,因此我可以单击它以获取有关的更多信息进入的每个部分。

HTML

<div class="slider">
  
  <div class="slider__slide slider__slide--1">
    <div class="slider__img slider__img--1"></div>
    <div class="slider__text slider__text--1">
      <h1 class="slider__header">Rejuvenate your, true self.</h1>
      <a href="ign.com" class="cta">discover</a>
    </div>
  </div>
  
  <div class="slider__slide slider__slide--2">
    <div class="slider__img slider__img--2"></div>
    <div class="slider__text slider__text--2">
      <h1 class="slider__header">Professonial, trust-worthy, and compassionate.</h1>
      <a href="google.com" class="cta">learn more</a>
    </div>
  </div>
  
    <div class="slider__slide slider__slide--3">
    <div class="slider__img slider__img--3"></div>
    <div class="slider__text slider__text--3">
      <h1 class="slider__header">Trust in us.</h1>
      <a href="youtube.com" class="cta">learn more</a>
    </div>
  </div>
  
  <div class="slider__slide slider__slide--4">
    <div class="slider__img slider__img--4"></div>
    <div class="slider__text slider__text--4">
      <h1 class="slider__header">What we do.</h1>
      <a href="tsn.ca" class="cta">discover</a>
    </div>
  </div>
  
  <div class="slider__navigation">
    <div class="slider__count slider__count--top">
      <p class="count count--top count--top-1">01</p>
      <p class="count count--top count--top-2">02</p>
      <p class="count count--top count--top-3">03</p>      
      <p class="count count--top count--top-4">04</p>
    </div>
    
    <div class="slider__bar">
      <div id="sliderBarDynamic" class="slider__bar--dynamic"></div>
      <div class="slider__bar--static"></div>
    </div>
    
     <div class="slider__count slider__count--bottom">
      <p class="count count--bottom count--bottom-1">02</p>
      <p class="count count--bottom count--bottom-2">03</p>
      <p class="count count--bottom count--bottom-3">04</p>      
      <p class="count count--bottom count--bottom-3">01</p>
    </div>
  </div>
  
</div>

CSS

@import url('https://fonts.googleapis.com/css2?family=Gilda+Display&family=Roboto&display=swap');

*,
*::before,
*::after{
padding:0;
margin:0;
box-sizing:inherit;
}

html{
font-size:16px;
box-sizing:border-box;
}

body{
font-family: 'Roboto', sans-serif;
color:#444444;
font-weight: 300;
line-height: 1.6;
}

img{
max-width:100%;
}

h1{
  font-size: 100px;
  color: #fff;
  font-family: 'Gilda Display', serif;
  font-weight: 300;
  line-height: 1;
}

h2{

}


h3{

}


P{
  color: #fff;
}

a{
text-decoration:none;
color:#ffffff;
  font-size: 24px;
}

ul{
list-style-type:none;
}



// Slider 
.slider{
  width: 100%;
  height: 100vh;
  overflow: hidden;
  position: relative;
  
  &__slide{
    width: 100%;
    height: 100%;
    display: flex;
    
    position: absolute;
    top: 0;
    left:0;
    
    &--1{
      z-index: 4;
    }
    
    &--2{
      z-index: 3;
    }
    
    &--3{
      z-index: 2;
    }
    
    &--4{
      z-index: 1;
    }
  }
  
  &__img{
    width: 100%;
    height: 100%;
    position: absolute;
    z-index: -1;
    background-size: cover;
    background-repeat: no-repeat;
    background-position: 50% 50%;
    
    &--1{
      background-image: url('https://i.postimg.cc/Y0T3F1tc/about-landing.jpg');
    }
    
    &--2{
      background-image: url('https://i.postimg.cc/FHHyKWyf/i-Stock-1148043788.jpg');
    }
    
    &--3{
      background-image: url('https://i.postimg.cc/tTqp06QH/i-Stock-1064136816.jpg');
    }
    
    &--4{
      background-image: url('https://i.postimg.cc/435R13K2/i-Stock-1179976698.jpg');
    }
  }
  
  &__text{
    align-self: flex-end;
    padding: 0 0 5vw 15vh;
    opacity: 0;
    width: 80%;
    max-width: 1005px;
    
    .slider__header{
      margin-bottom: 40px;
      text-transform: capitalize;
    }
    
    .cta{
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 6px;
      margin-left: 65px;
      position: relative;
      
      &:before{
        content: '';
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        left: -55px;
        width: 40px;
        height: 1px;
        background-color: white;
      }
    }
  }
  
  // Slider Navigation
  &__navigation{
    width: 21px;
    height: 400px;
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    left: calc(100% - 5vw);
    z-index: 10;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
  }
  
  .count--top{
    
      position: absolute;
      top: 0;
      left: 0;
    
    // position:
  }
  
  .count{
    opacity: 0;
  }
  
  .count:first-child{
    opacity: 1;
  }
  
  .count--bottom{
    position: absolute;
    bottom: 0;
    left: 0;
  }
  
  &__bar{
    width: 2px;
    height: 250px;
    position: relative;
    
    &--dynamic{
      width: 100%;
      height: 100%;
      background-color: #FF69B4;
      transform-origin: top center;
      
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
    }
    
    &--static{
      width: 100%;
      height: 100%;
      background-color: darkgrey;
      
      position: absolute;
      top: 0;
      left: 0;
    }
  }
}
// Slider end

Javascript

/*
*/

// timeline to control animations everytime the timeline restarts/repeats
let tlRepeat = gsap.timeline();

// Need first slides elements - img and text to animate on each repeat of timeline
let repeatBeginning = ()=>{
  gsap.set(bgImage[0], {opacity: 0, scale: 1.2, webkitFilter:"blur(" + 6 + "px)"})  

 tlRepeat
  .add("slide1-in")
  .fromTo([countTop[0], countBottom[0]], {opacity: 0}, {duration: 0.3, opacity: 1, ease: "Power2.easeIn"}, "slide1-in")  
  .to(bgImage[0], {duration: 1.8, scale: 1, opacity: 1, webkitFilter:"blur(" + 0 + "px)"}, "slide1-in")
  .fromTo(text[0], {opacity: 0, x: -30, ease: "Power2.easeIn"}, {duration: 0.8, opacity: 1, x: 0}, "-=1")


}

// On start animations
// let onStartSlide1Animations = ()=>{
//     // gsap.to(text[0], {duration: 0.7, opacity: 1, x: -15, ease: "Power2.easeIn"})
// }

// Variables
let slides = document.querySelectorAll('.slider__slide'),
    dynamicBar = document.querySelector('#sliderBarDynamic'),
    countTop = document.querySelectorAll(".count--top"),
    countBottom = document.querySelectorAll(".count--bottom"),
    bgImage = document.querySelectorAll(".slider__img"),
    text = document.querySelectorAll(".slider__text"),
    tl = gsap.timeline({repeat: 0, delay: 1, paused: false, onRepeat: repeatBeginning});
    
// Push all text back and only make first one visible
gsap.set(text, {x: -30});
gsap.set(text[0], {opacity: 1});


// Animate slide's elements but not the first one. 
// Make first slide's elements animate when timeline is repeating, 
// Follow the flow of rest of the slide's animations
slides.forEach((slide, i) =>{
  
  tl
    .fromTo(dynamicBar, {scaleY: 0}, {duration: 1.4, scaleY: 1}, "+=2")
    .set(dynamicBar, {transformOrigin: "bottom center"})
    .to(dynamicBar, {duration: 1, scaleY: 0}, "+=0.4")
    .set(dynamicBar, {transformOrigin: "top center"})
    .add("elements-in-out")
    .to([countTop[i], countBottom[i]], {opacity: 0}, "elements-in-out")  
    .to([countTop[i+1], countBottom[i+1]], {opacity: 1}, "elements-in-out")
    .to(bgImage[i], {duration: 0.2, opacity: 0}, "elements-in-out")
    .set(bgImage[i+1], {scale: 1.2, webkitFilter:"blur(" + 6 + "px)"}, "elements-in-out")
    .to(bgImage[i+1], {duration: 1.8, scale: 1, webkitFilter:"blur(" + 0 + "px)"}, "elements-in-out")
    .to(text[i], {duration: 0.3, opacity: 0}, "elements-in-out")
    .to(text[i+1], {duration: 0.8, opacity: 1, x: 0}, "-=1")
})

所以首先,为了 re-imagine ReactJS 的任何 html 片段,尝试在片段中寻找相似之处/重复。一旦你这样做了,你就会明白什么可以作为单独的组件分离出来,以及它们是如何联系在一起的。

现在,只需查看 html 片段,我们就可以看到幻灯片是重复的,因此它们可以作为一个单独的组件保存。我们还看到每张幻灯片都显示一个独特的标题和段落文本,并且还有一个配色方案。因此,为了使我们的 Slide 组件动态化,我们可以将这些作为 props 传递给组件。

所以我们创建整个 Slider 组件(由 Slide 组成)的 data 看起来像这样:

const sliderData = [
    {
        id: '1',
        headerText: `I'm the first Box`,
        paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
            elit. Integer lacinia dui lectus. Donec scelerisque ipsum
            diam, ac mattis orci pellentesque eget. `,
        buttonText: 'Check Now',
        colors: {
            sliderBox: '#500033',
            sliderIllustration: '#FF0077',
            sliderInner: 'rgba(255, 0, 119, 0.4)',
            sliderButton: '#FF0077',
        },
    },
    ...
]

接下来我们注意到,一些 UI 是使用 CSS 创建的,而这个 主要是 导致整个代码段是静态的。因此,为了实现 动态性 我们根据获取的数据在组件中渲染了一些样式。像这样:

<style
    dangerouslySetInnerHTML={{
        __html: [
        `
            .slider {
                display: flex;
                width: ${sliderData.length * 100}%; /*(this value in css was static 500%, to rendered only 5 slides)*/
                height: 55rem;
                transition: all 0.25s ease-in;
                transform: translateX(0);
            }

            .trail {
                bottom: 5%;
                left: 50%;
                transform: translateX(-50%);
                width: 60%;
                display: grid;
                grid-template-columns: repeat(${sliderData.length}, 1fr); /*this value was also static in css to display only 5 bars/pages.*/
                gap: 1rem;
                text-align: center;
                font-size: 1.5rem;
            }`,
        ].join('\n'),
    }}
></style>

此外,每张幻灯片都分配了静态 class,如 box1, box2... etc。因此,需要像这样为每张幻灯片呈现这些样式:

<style
    dangerouslySetInnerHTML={{
        __html: [
            `.slider .box${data.id} {
                background-color: ${data.colors.sliderBox};
            }
            .slider .box${data.id} .illustration .inner {
                background-color: ${data.colors.sliderIllustration};
            }
            .slider .box${data.id} .illustration .inner::after, .slider .box${data.id} .illustration .inner::before {
                background-color: ${data.colors.sliderInner};
            }
            .slider .box${data.id} button {
                background-color: ${data.colors.sliderButton};
            }`,
        ].join('\n'),
    }}
></style>

(请注意,上面代码片段中的 object data 对应于构成整个滑块组件的数组 sliderData 中的 object。

一旦我们 UI 准备就绪,引入 GSAP 动画就很简单了。首先,我们为 GSAP 在动画过程中使用的 UI 个元素创建了 refs

接下来,我们触发动画就像您在普通 javascript 中一样。这里唯一的区别是我们在 useEffect 挂钩中执行它们,该挂钩在功能组件加载后运行(空依赖数组)。

此外,还有一些 hard-coded 内容仅对五张幻灯片进行动画处理。我们通过引入 let ratio = 100 / sliderData.length

使其动态化

整个组件:

import React, { useEffect, useRef } from 'react'
import './styleNew.css'
import { gsap } from 'gsap'

const sliderData = [
  {
    id: '1',
    headerText: `I'm the first Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#500033',
      sliderIllustration: '#FF0077',
      sliderInner: 'rgba(255, 0, 119, 0.4)',
      sliderButton: '#FF0077',
    },
  },
  {
    id: '2',
    headerText: `I'm the second Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#000050',
      sliderIllustration: '#0033FF',
      sliderInner: 'rgba(0, 51, 255, 0.4)',
      sliderButton: '#0033FF',
    },
  },
  {
    id: '3',
    headerText: `I'm the third Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#00501D',
      sliderIllustration: '#00FF44',
      sliderInner: 'rgba(0, 255, 68, 0.4)',
      sliderButton: '#00FF44',
    },
  },
  {
    id: '4',
    headerText: `I'm the fourth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#554D00',
      sliderIllustration: '#FF4E00',
      sliderInner: 'rgba(255, 78, 0, 0.4)',
      sliderButton: '#FF4E00',
    },
  },
  {
    id: '5',
    headerText: `I'm the fifth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#300050',
      sliderIllustration: '#8000FF',
      sliderInner: 'rgba(128, 0, 255, 0.4)',
      sliderButton: '#8000FF',
    },
  },
  {
    id: '6',
    headerText: `I'm the sixth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#000050',
      sliderIllustration: '#0033FF',
      sliderInner: 'rgba(0, 51, 255, 0.4)',
      sliderButton: '#0033FF',
    },
  },
  {
    id: '7',
    headerText: `I'm the seventh Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#00501D',
      sliderIllustration: '#00FF44',
      sliderInner: 'rgba(0, 255, 68, 0.4)',
      sliderButton: '#00FF44',
    },
  },
  {
    id: '8',
    headerText: `I'm the eighth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#554D00',
      sliderIllustration: '#FF4E00',
      sliderInner: 'rgba(255, 78, 0, 0.4)',
      sliderButton: '#FF4E00',
    },
  },
  {
    id: '9',
    headerText: `I'm the ninth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#300050',
      sliderIllustration: '#8000FF',
      sliderInner: 'rgba(128, 0, 255, 0.4)',
      sliderButton: '#8000FF',
    },
  },
]

export default function SliderNew() {
  const slider = useRef(undefined)
  const prevButton = useRef(undefined)
  const nextButton = useRef(undefined)
  const trail = useRef([])

  useEffect(() => {
    console.log('sliders', slider)
    console.log('trail', trail)
    console.log('nextButton.current', nextButton.current)
    console.log('prevButton.current', prevButton.current)
    startGsapAnimations()
  }, [])

  const startGsapAnimations = () => {
    // Transform value
    let value = 0
    // trail index number
    let trailValue = 0
    // interval (Duration)
    let interval = 10000

    let ratio = 100 / sliderData.length

    const tl = gsap.timeline({
      defaults: { duration: 0.6, ease: 'power2.inOut' },
    })
    tl.from('.bg', { x: '-100%', opacity: 0 })
      .from('p', { opacity: 0 }, '-=0.3')
      .from('h1', { opacity: 0, y: '30px' }, '-=0.3')
      .from('button', { opacity: 0, y: '-40px' }, '-=0.8')

    // function to restart animation
    const animate = () => tl.restart()

    const slide = (condition) => {
      // CLear interval
      clearInterval(start)
      // update value and trailValue
      condition === 'increase' ? initiateINC() : initiateDEC()
      // move slide
      move(value, trailValue)
      // Restart Animation
      animate()
      // start interal for slides back
      start = setInterval(() => slide('increase'), interval)
    }

    // function for increase(forward, next) configuration
    const initiateINC = () => {
      // Remove active from all trails
      sliderData.forEach((_item, index) =>
        trail[index].classList.remove('active'),
      )
      // increase transform value
      //   console.log('initialInc~value', value)
      //   console.log('initialInc~calc', (sliderData.length - 1) * ratio)
      //   console.log(
      //     'initialInc~eq',
      //     Math.round(value) === Math.round((sliderData.length - 1) * ratio),
      //   )
      Math.round(value) === Math.round((sliderData.length - 1) * ratio)
        ? (value = 0)
        : (value += ratio)
      // update trailValue based on value
      trailUpdate()
    }

    // function for decrease(backward, previous) configuration
    const initiateDEC = () => {
      // Remove active from all trails
      sliderData.forEach((_item, index) =>
        trail[index].classList.remove('active'),
      )
      // decrease transform value
      Math.round(value) === 0
        ? (value = (sliderData.length - 1) * ratio)
        : (value -= ratio)
      // update trailValue based on value
      trailUpdate()
    }

    // function to transform slide
    const move = (S, T) => {
      // transform slider
      slider.current.style.transform = `translateX(-${S}%)`
      //add active class to the current trail
      console.log('trail', T)
      trail[Math.round(T)].classList.add('active')
    }

    const trailUpdate = () => {
      trailValue = value / ratio
      console.log('trailUpdate', trailValue)
    }

    // Start interval for slides
    let start = setInterval(() => slide('increase'), interval)

    nextButton.current.addEventListener('click', () => slide('increase'))
    prevButton.current.addEventListener('click', () => slide('decrease'))

    const clickCheck = (e) => {
      // CLear interval
      clearInterval(start)
      // Get selected trail
      const check = e.target

      // remove active class from all trails
      sliderData.forEach((_item, index) => {
        trail[index].classList.remove('active')
        if (check === trail[index]) {
          value = index * ratio
        }
      })

      // add active class
      check.classList.add('active')

      // update trail based on value
      trailUpdate()
      // transfrom slide
      move(value, trailValue)
      // start animation
      animate()
      // start interval
      start = setInterval(() => slide('increase'), interval)
    }

    // Add function to all trails
    sliderData.forEach((_item, index) =>
      trail[index].addEventListener('click', (ev) => clickCheck(ev)),
    )
  }

  return (
    <>
      <style
        dangerouslySetInnerHTML={{
          __html: [
            `
              .slider {
                display: flex;
                width: ${sliderData.length * 100}%;
                height: 55rem;
                transition: all 0.25s ease-in;
                transform: translateX(0);
              }
              
              .trail {
                bottom: 5%;
                left: 50%;
                transform: translateX(-50%);
                width: 60%;
                display: grid;
                grid-template-columns: repeat(${sliderData.length}, 1fr);
                gap: 1rem;
                text-align: center;
                font-size: 1.5rem;
              }`,
          ].join('\n'),
        }}
      ></style>
      <div className="container">
        <div className="slider" ref={slider}>
          {sliderData.map((item, index) => (
            <Slide key={index} data={item} />
          ))}
        </div>
        <Svg
          ref={prevButton}
          compClass="prev"
          compTransform="translate(0 91) rotate(-90)"
        />
        <Svg
          ref={nextButton}
          compClass="next"
          compTransform="translate(56.898) rotate(90)"
        />
        <div className="trail">
          {sliderData.map((item, index) => (
            <div
              ref={(ref) => {
                trail[index] = ref
              }}
              key={index}
              className={index == 0 ? `box${item.id} active` : `box${item.id}`}
            >
              {item.id}
            </div>
          ))}
        </div>
      </div>
    </>
  )
}

function Slide(props) {
  const { data } = props
  return (
    <>
      <style
        dangerouslySetInnerHTML={{
          __html: [
            `.slider .box${data.id} {
                background-color: ${data.colors.sliderBox};
              }
              .slider .box${data.id} .illustration .inner {
                background-color: ${data.colors.sliderIllustration};
              }
              .slider .box${data.id} .illustration .inner::after, .slider .box${data.id} .illustration .inner::before {
                background-color: ${data.colors.sliderInner};
              }
              .slider .box${data.id} button {
                background-color: ${data.colors.sliderButton};
              }`,
          ].join('\n'),
        }}
      ></style>
      <div className={`box${data.id} box`}>
        <div className="bg"></div>
        <div className="details">
          <h1>{data.headerText}</h1>
          <p>{data.paragraphText}</p>
          <button>{data.buttonText}</button>
        </div>

        <div className="illustration">
          <div className="inner"></div>
        </div>
      </div>
    </>
  )
}

const Svg = React.forwardRef((props, ref) => {
  console.log('Svg', props)
  return (
    <svg
      ref={ref}
      xmlns="http://www.w3.org/2000/svg"
      width="56.898"
      height="91"
      className={props.compClass}
      viewBox="0 0 56.898 91"
    >
      <path
        fill="#fff"
        d="M45.5,0,91,56.9,48.452,24.068,0,56.9Z"
        transform={props.compTransform}
      ></path>
    </svg>
  )
})

styleNew.css:

*,
*:before,
*:after {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

html {
  box-sizing: border-box;
  font-family: "Roboto", sans-serif;
  font-size: 62.5%;
}
@media only screen and (max-width: 800px) {
  html {
    font-size: 57%;
  }
}

body {
  background-color: #000;
  color: #fff;
  padding: 8rem;
}
@media only screen and (max-width: 1000px) {
  body {
    padding: 0;
  }
}

.container {
  position: relative;
  overflow: hidden;
  border-radius: 5rem;
}
@media only screen and (max-width: 1000px) {
  .container {
    border-radius: 0;
  }
}

@media only screen and (max-width: 1000px) {
  .slider {
    height: 100vh;
  }
}
.slider .box {
  height: 100%;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: center;
  overflow: hidden;
  position: relative;
}
@media only screen and (max-width: 650px) {
  .slider .box {
    grid-template-columns: 1fr;
    grid-template-rows: repeat(2, 1fr);
  }
}
.slider .box .bg {
  padding: 2rem;
  background-color: rgba(0, 0, 0, 0.2);
  width: 55%;
  transform: skewX(7deg);
  position: absolute;
  height: 100%;
  left: -10%;
  padding-left: 20rem;
  transform-origin: 0 100%;
}
@media only screen and (max-width: 800px) {
  .slider .box .bg {
    width: 65%;
  }
}
@media only screen and (max-width: 650px) {
  .slider .box .bg {
    width: 100%;
    left: 0;
    bottom: 0;
    height: 54%;
    transform: skewX(0deg);
  }
}
.slider .box .bg::before {
  content: "";
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  background-color: inherit;
  pointer-events: none;
  transform: skewX(10deg);
}
@media only screen and (max-width: 650px) {
  .slider .box .bg::before {
    width: 120%;
    bottom: 0;
    transform: skewX(0deg);
  }
}
.slider .box .details {
  padding: 5rem;
  padding-left: 10rem;
  z-index: 100;
  grid-column: 1/span 1;
  grid-row: 1/-1;
}
@media only screen and (max-width: 650px) {
  .slider .box .details {
    grid-row: 2/span 1;
    grid-column: 1/-1;
    text-align: center;
    padding: 2rem;
    transform: translateY(-9rem);
  }
}
.slider .box .details h1 {
  font-size: 3.5rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}
.slider .box .details p {
  display: inline-block;
  font-size: 1.3rem;
  color: #B5B4B4;
  margin-bottom: 2rem;
  margin-right: 5rem;
}
@media only screen and (max-width: 800px) {
  .slider .box .details p {
    margin-right: 0;
  }
}
.slider .box .details button {
  padding: 1rem 3rem;
  color: #fff;
  border-radius: 2rem;
  outline: none;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
}
.slider .box .details button:hover {
  opacity: 0.8;
}
.slider .box .details button:focus {
  outline: none;
  border: none;
}

.slider .illustration {
  grid-column: 2/-1;
  grid-row: 1/-1;
  justify-self: center;
}
@media only screen and (max-width: 650px) {
  .slider .illustration {
    grid-row: 1/span 1;
    grid-column: 1/-1;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
.slider .illustration div {
  height: 25rem;
  width: 18rem;
  border-radius: 3rem;
  background-color: #FF0077;
  position: relative;
  transform: skewX(-10deg);
}
@media only screen and (max-width: 800px) {
  .slider .illustration div {
    height: 20rem;
    width: 15rem;
  }
}
.slider .illustration div::after, .slider .illustration div::before {
  content: "";
  position: absolute;
  height: 100%;
  width: 100%;
  border-radius: 3rem;
  top: 0;
  left: 0;
}
.slider .illustration div::after {
  transform: translate(4rem, -1rem);
}
.slider .illustration div::before {
  transform: translate(2rem, -2rem);
}

.prev,
.next,
.trail {
  z-index: 10000;
  position: absolute;
}

.prev,
.next {
  width: 4rem;
  cursor: pointer;
  opacity: 0.2;
  transition: all 0.3s ease;
}
@media only screen and (max-width: 650px) {
  .prev,
.next {
    display: none;
  }
}
.prev:hover,
.next:hover {
  opacity: 1;
}

.prev {
  top: 50%;
  left: 2%;
  transform: translateY(-50%);
}

.next {
  top: 50%;
  right: 2%;
  transform: translateY(-50%);
}


@media only screen and (max-width: 650px) {
  .trail {
    width: 90%;
    bottom: 13%;
  }
}
.trail div {
  padding: 2rem;
  border-top: 3px solid #fff;
  cursor: pointer;
  opacity: 0.3;
  transition: all 0.3s ease;
}
.trail div:hover {
  opacity: 0.6;
}
@media only screen and (max-width: 650px) {
  .trail div {
    padding: 1rem;
  }
}

.active {
  opacity: 1 !important;
}