---
title: 'Storefront: Practical Lab - Sticky Add to Cart | Shopware Community Hub'
description: >-
  Build a mobile sticky add-to-cart bar on the PDP using Twig, SCSS, and
  storefront JavaScript, including scroll-based visibility and smooth
  transitions.
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-practical-lab-sticky-add-to-cart
---

# Storefront: Practical Lab - Sticky Add to Cart

<LearningObjectives>

- Understand how to implement a mobile-only storefront feature based on viewport conditions.
- Implement visibility logic based on scroll position and viewport detection.
- Learn how to reuse existing buy form logic instead of duplicating functionality.
- Apply SCSS to control visibility states and implement smooth transitions.
- Understand how to integrate Twig, SCSS, and storefront JavaScript to build a consistent user experience on the PDP.

</LearningObjectives>

# Storefront: Practical Lab - Sticky Add to Cart

In this practical lab, you will build a mobile-only **sticky add-to-cart bar** for the product detail page (PDP).

This component improves the user experience on mobile devices by keeping the add-to-cart button always visible, even when the original buy form is no longer visible.

To achieve this, you will:

- Extend storefront templates with Twig (`sw_extends` and block overrides).
- Connect Twig markup to storefront JavaScript via `data-*` attributes.
- Implement a custom storefront JavaScript plugin (`init()`, `update()`, and safe event binding).
- Work with responsive behavior using the Shopware core helper `ViewportDetection`.
- Improve the user experience with a smooth CSS transition (JavaScript controls the state, CSS handles visibility effect and animation).

In this lab, “mobile-only” is implemented using the Bootstrap breakpoints **XS** and **SM** (width-based). This means that some devices in landscape mode may switch to a larger breakpoint and the sticky bar will be disabled.

This practical lab is based on the reference plugin [FrontendDevIntermediateStickyAddToCart](https://github.com/ShopwareAcademy/FrontendDevIntermediateStickyAddToCart).

## Target Result

By the end of this practical lab, you will have built a fully functional mobile sticky add-to-cart bar with the following behavior:

- A sticky add-to-cart container that exists in the PDP template but is only visible on mobile (XS/SM).
- The sticky bar becomes visible when the original buy form scrolls out of the viewport.
- Clicking the sticky button triggers the **original** buy form submission, so no cart logic is duplicated.
- A smooth show/hide animation using CSS transitions, controlled by a single state class `is-visible`.

## Folder Structure

At the end of the implementation, your storefront folder structure will look like this:

```text
[shop_root]
 └─ custom
    └─ plugins
       └─ [your_plugin]
           ├─ src
           │  ├─ Resources
           │  │  │─ app
           │  │  │  └─ storefront
           │  │  │     │─ dist
           │  │  │     │  └─ storefront
           │  │  │     │     └─ js
           │  │  │     │        └─ ...
           │  │  │     └─ src
           │  │  │        │─ plugin
           │  │  │        │  └─ product-detail
           │  │  │        │     └─ sticky-add-to-cart.plugin.js
           │  │  │        ├─ scss
           │  │  │        │  ├─ component
           │  │  │        │  │  └─ _sticky-add-to-cart.scss
           │  │  │        │  └─ base.scss
           │  │  │        └─ main.js
           │  │  └─ views
           │  │     └─ storefront
           │  │        └─ page
           │  │           └─ content
           │  │              └─ product-detail.html.twig
           │  └─ [YourPluginName].php
           └─ composer.json
```

## Workflow

The workflow is as follows:

- You create a custom template in `product-detail.html.twig`.
- You bind your storefront JavaScript plugin to the template.
- Your storefront JavaScript controls the behavior of the sticky bar and the add-to-cart action.
- You add styling to show/hide the sticky bar and add the smooth show/hide animation.

In the next steps, you will build this structure step by step.

## Step 1: Create a Twig Template

To implement the sticky-add-to-cart, you first need to create a Twig template that renders the sticky bar. This template will be injected into the PDP template.

### Step 1.1: Find the PDP Template

First, you need to find a suitable location in the PDP where your template can be added.

![PDP Content Element](assets/images/storefront-pdp-content-element.jpg)

In this case, it is the `<div class="container-main">` element, which represents the main content area of the PDP.

Copy the HTML class and search for it in the Shopware vendor folder.

![Vendor Find Template](assets/images/vendor-folder-find-correct-twig-file.jpg)

You will find multiple matches for `container-main`. The correct one is the `product-detail.html.twig` file. Copy its path:

![Vendor Copy Template Path](assets/images/vendor-folder-twig-file-copy-path.jpg)

From your plugin root folder, right-click on the `src` folder (`[your_plugin]/src`) and paste the copied path. This will create the file in the correct location.

![Plugin Create Template File](assets/images/plugin-create-template-file.jpg)

![Plugin Create Template File Add Path](assets/images/plugin-create-template-file-add-path.jpg)

![Plugin Template File Created](assets/images/plugin-create-template-file-created.jpg)

At this point, your folder structure should look like this:

```text
[shop_root]
 └─ custom
    └─ plugins
       └─ [your_plugin]
           ├─ src
           │  ├─ Resources
           │  │  └─ views
           │  │     └─ storefront
           │  │        └─ page
           │  │           └─ content
           │  │              └─ product-detail.html.twig
           │  └─ [YourPluginName].php
           └─ composer.json
```

### Step 1.2: Add the Template Code

Now add the following code to the template:

```twig
{% sw_extends '@Storefront/storefront/page/content/product-detail.html.twig' %}

{% block base_main_inner %}
    {{ parent() }}

    {# Mobile-only sticky add-to-cart bar. Visibility is controlled by the JS plugin. #}
    <div class="academy-sticky-add-to-cart-container fixed-top d-md-none bg-body border-bottom shadow-sm p-3 z-3"
         data-sticky-add-to-cart="true"
    >
        <button type="button"
                class="btn btn-primary w-100"
                {# JS hook: We use a dedicated data attribute so the selector stays stable even if the markup changes (e.g., more buttons). #}
                data-sticky-add-to-cart-button="true"
        >
            {{ 'detail.addProduct'|trans|sw_sanitize }}
        </button>
    </div>
{% endblock %}
```

This will add your template to the DOM.

![Storefront PDP Template Created](assets/images/storefront-pdp-template-created.jpg)

At this point, the element is not visible for desktop viewports but appears on mobile at the top of the page.

This behavior is controlled by the Bootstrap utility classes:

- `d-md-none`: Hides the element in medium screens and larger.
- `fixed-top`: Positions the element at the top of the viewport (`position: fixed; top: 0;`).
- `z-3`: Ensures the element is rendered above other elements, so no other elements can overlap it.

The attribute `data-sticky-add-to-cart="true"` is used to bind your custom template to your JavaScript plugin in the next step. Technically, the selector would also work if the attribute existed without the value `"true"`. However, using `="true"` is a common convention in the Shopware core for simple `data-*` marker attributes. The important part is that the attribute exists and matches the selector `[data-sticky-add-to-cart]`.

Now the template is created successfully. In the next step, you will add the behavior using JavaScript.

## Step 2: Add JavaScript Behavior

Now you will connect your Twig template with your storefront JavaScript plugin.

### Step 2.1: Create a JavaScript Plugin

To save time, instead of generating the storefront plugin via command line and restructuring it afterward, create the files manually as in the following:

```text
[shop_root]
 └─ custom
    └─ plugins
       └─ [your_plugin]
           ├─ src
           │  ├─ Resources
           │  │  │─ app // --> Create this folder
           │  │  │  └─ storefront // --> Create this folder
           │  │  │     └─ src // --> Create this folder
           │  │  │        │─ plugin // --> Create this folder
           │  │  │        │  └─ product-detail // --> Create this folder
           │  │  │        │     └─ sticky-add-to-cart.plugin.js // --> Create this file
           │  │  │        └─ main.js // --> Create this file
           │  │  └─ views
           │  │     └─ storefront
           │  │        └─ page
           │  │           └─ content
           │  │              └─ product-detail.html.twig
           │  └─ [YourPluginName].php
           └─ composer.json
```

When you are using PhpStorm, right-click on `Resources` and select `New > File` and paste the path:

```bash
app/storefront/src/plugin/product-detail/sticky-add-to-cart.plugin.js
```

This will create the file and the folder structure.

When you are using Visual Studio Code, do the same: right-click on `Resources` and select `New File` and paste the path `app/storefront/src/plugin/product-detail/sticky-add-to-cart.plugin.js`.

Now also create the `main.js` file:

```bash
app/storefront/src/main.js
```

### Step 2.2: Register the JavaScript Plugin

In the `sticky-add-to-cart.plugin.js` file, add the following code:

```js
const { PluginBaseClass } = window;

export default class StickyAddToCart extends PluginBaseClass {
  init() {
  }
}
```

Now register the storefront plugin asynchronously in the `main.js` file and bind it to your Twig template via the `data-sticky-add-to-cart` attribute:

```js
const PluginManager = window.PluginManager;

PluginManager.register(
    'StickyAddToCart',
    () => import('./plugin/product-detail/sticky-add-to-cart.plugin'),
    '[data-sticky-add-to-cart]'
);
```

<Callout title="Reminder: Binding" type="info">

The recommended way to bind JavaScript to your template is via `data-*` attributes.

However, it is also possible to bind your template using the `class` or `id`. And the binding for these three looks like this:

1. For data-attributes, you wrap it with brackets (`[ ]`): `[data-sticky-add-to-cart]`.
2. For classes, you use the dot notation (`.`): `.sticky-add-to-cart`.
3. For IDs, you use `#`: `#sticky-add-to-cart`.

</Callout>

### Step 2.3: Verify the JavaScript Plugin

Now build the storefront:

```bash
bin/build-storefront.sh
```

Shopware CLI equivalent:

```bash
shopware-cli project storefront-build
```

<Callout title="Reminder: Build Storefront" type="info">

This command creates a `dist` folder, which contains the compiled JavaScript used by the storefront.

</Callout>

Open your local shop (startpage) in the browser and run the following command in the browser's console:

```js
PluginManager.getPlugin('StickyAddToCart')
```

Your plugin should be registered:

![Browser Console getPlugin Command](assets/images/storefront-browser-console-getPlugin-command.jpg)

At this point, **no instance is created yet**, which is expected.

Now navigate to a PDP and run the same command again in the browser's console.

![Browser Console getPlugin Command in PDP](assets/images/storefront-browser-console-pdp-getPlugin-command.jpg)

On the PDP, the plugin instance is created. This confirms that the storefront plugin is correctly bound to your Twig template.

### Step 2.4: Visibility Control

Before implementing the logic, let's define what the plugin needs to do:

- Detect whether the current viewport is mobile.
- Detect whether the original buy form is still visible.
- React to scroll and resize events.
- Update the visibility of the sticky bar accordingly.

---

To implement the visibility control, you first need a way to detect whether the customer is using a mobile viewport. For this, you can use the `ViewportDetection` helper from the Shopware storefront core.

Let's start by creating a private method `_isMobileViewport()`:

```js
import ViewportDetection from 'src/helper/viewport-detection.helper';

const { PluginBaseClass } = window;

export default class StickyAddToCart extends PluginBaseClass {
  init() {
  }

  _isMobileViewport() {
    return true === ViewportDetection.isXS() || true === ViewportDetection.isSM();
  }
}
```

The `ViewportDetection` helper provides methods to detect the current viewport size. In this case, we only need the `isXS()` and `isSM()`.

---

Next, you need to implement the visibility logic. To do this, you need:

- A reference to the original buy form.
- A way to react to scroll and resize events.
- A method that updates the visibility state.

The goal is:

When a user scrolls the PDP on a mobile viewport, the sticky bar should only be visible once the original buy form is no longer in view.

```js
import ViewportDetection from 'src/helper/viewport-detection.helper';

const { PluginBaseClass } = window;

export default class StickyAddToCart extends PluginBaseClass {

  static options = {
    pdpBuyFormSelector: 'form.buy-widget[data-add-to-cart="true"]',
    pdpBuyButtonSelector: 'button.btn-buy[type="submit"], button[type="submit"]',
    stickyButtonSelector: '[data-sticky-add-to-cart-button]',
  };

  // Instance variable declaration (for more readability)
  _pdpBuyForm = null;
  _stickyButton = null;
  _onScroll = null;
  _onResize = null;

  init() {
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);
    this._stickyButton = this.el.querySelector(this.options.stickyButtonSelector);

    if (null !== this._stickyButton) {
      this._stickyButton.addEventListener('click', () => this._triggerAddToCart());
    }

    this._onScroll = () => this._syncVisibility();
    this._onResize = () => this._syncVisibility();

    // When the window is scrolled / resized, we need to sync the visibility again.
    window.addEventListener('scroll', this._onScroll, {passive: true});
    window.addEventListener('resize', this._onResize, {passive: true});

    // Initial state
    this._syncVisibility();
  }

  update() {
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);
    this._syncVisibility();
  }

  _isMobileViewport() {
    return true === ViewportDetection.isXS() || true === ViewportDetection.isSM();
  }

  _syncVisibility() {
    // Re-query to avoid stale references after DOM updates.
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);

    if (null === this._pdpBuyForm) {
      this.el.classList.remove('is-visible');
      return;
    }

    const rect = this._pdpBuyForm.getBoundingClientRect();
    const isBuyFormOutOfView = rect.bottom <= 0;

    const isMobile = this._isMobileViewport();

    // Show only on mobile AND only after the original buy form scrolled out.
    if (true === isMobile && true === isBuyFormOutOfView) {
      this.el.classList.add('is-visible');
    } else {
      this.el.classList.remove('is-visible');
    }
  }
}
```

**Explanation:**

- The static `buyFormSelector` option is used to identify the original buy form.
- The `_buyForm` instance variable is used to store a reference to that form.

- In the `init()` method:
   - The buy form is queried from the DOM.
   - The `scroll` and `resize` event listeners are registered.
   - Both events trigger the private `_syncVisibility()` method.
   - The `{ passive: true }` option tells the browser that this listener will not call `event.preventDefault()`.
     This helps the browser keep scrolling smooth (especially on mobile).
   - The private `_syncVisibility()` method is called once to set the initial state.

- In the private `_syncVisibility()` method:
   - The `_buyForm` instance variable is re-queried to avoid stale references after DOM updates.
   - If the buy form does not exist, the sticky bar is hidden (`is-visible` class is removed from the sticky bar).
   - It checks if the viewport is mobile.
   - It checks if the original buy form is out of view by using the `getBoundingClientRect()` method.
   - If the viewport is mobile and the original buy form is out of view, the sticky bar becomes visible (by adding the `is-visible` class).
   - If the viewport is not mobile, the sticky bar is not visible (by removing the `is-visible` class).

- In the `update()` method:
   - The `_buyForm` instance variable is re-queried
   - The visibility is recalculated after DOM updates.

Now build your storefront and check the behavior.

At this point the sticky bar is visible only on the mobile viewport. But it is visible all the time, and the add-to-cart action of the sticky bar is not working yet.

![Sticky Add To Cart State One](assets/images/storefront-pdp-sticky-add-to-cart-mobile-viewport-state-one.jpg)

### Step 2.5: Add To Cart Action

At this point, two steps are required to finish the implementation:

1. Add the add-to-cart action to the sticky bar (JavaScript).
2. Add the visibility states and the smooth transition effect (SCSS, in Step 3).

Let's start with the add-to-cart action.

The goal is to reuse the existing buy form logic instead of duplicating it. To achieve this, you listen for a click (click event) on the sticky add-to-cart button and trigger the original buy form's submit action.

This is important: The sticky button should not create its own add-to-cart request. The original buy form already knows the selected quantity, variant state, hidden inputs, validation behavior, and Shopware's add-to-cart handling. Your sticky button only forwards the action to that existing form.

To implement this:

1. Add your sticky add-to-cart button and the buy-button from the original buy form to the static options.
2. Register a `click` event to the sticky bar.
3. Trigger the click of the original buy form.

```js
import ViewportDetection from 'src/helper/viewport-detection.helper';

const { PluginBaseClass } = window;

export default class StickyAddToCart extends PluginBaseClass {

  static options = {
    pdpBuyFormSelector: 'form.buy-widget[data-add-to-cart="true"]',
    pdpBuyButtonSelector: 'button.btn-buy[type="submit"], button[type="submit"]',
    stickyButtonSelector: '[data-sticky-add-to-cart-button]',
  };

  // Instance variable declaration (for more readability)
  _pdpBuyForm = null;
  _stickyButton = null;
  _onScroll = null;
  _onResize = null;

  init() {
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);
    this._stickyButton = this.el.querySelector(this.options.stickyButtonSelector);

    if (null !== this._stickyButton) {
      this._stickyButton.addEventListener('click', () => this._triggerAddToCart());
    }

    this._onScroll = () => this._syncVisibility();
    this._onResize = () => this._syncVisibility();

    // When the window is scrolled / resized, we need to sync the visibility again.
    window.addEventListener('scroll', this._onScroll, {passive: true});
    window.addEventListener('resize', this._onResize, {passive: true});

    // Initial state
    this._syncVisibility();
  }

  update() {
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);
    this._syncVisibility();
  }

  _isMobileViewport() {
    return true === ViewportDetection.isXS() || true === ViewportDetection.isSM();
  }

  _syncVisibility() {
    // Re-query to avoid stale references after DOM updates.
    this._pdpBuyForm = document.querySelector(this.options.pdpBuyFormSelector);

    if (null === this._pdpBuyForm) {
      this.el.classList.remove('is-visible');
      return;
    }

    const rect = this._pdpBuyForm.getBoundingClientRect();
    const isBuyFormOutOfView = rect.bottom <= 0;

    const isMobile = this._isMobileViewport();

    // Show only on mobile AND only after the original buy form scrolled out.
    if (true === isMobile && true === isBuyFormOutOfView) {
      this.el.classList.add('is-visible');
    } else {
      this.el.classList.remove('is-visible');
    }
  }

  // Add this function
  _triggerAddToCart() {
    if (null === this._pdpBuyForm) {
      return;
    }

    const pdpBuyButton = this._pdpBuyForm.querySelector(this.options.pdpBuyButtonSelector);
    if (null === pdpBuyButton || true === pdpBuyButton.disabled) {
      return;
    }

    if (typeof this._pdpBuyForm.requestSubmit === 'function') {
      try {
        this._pdpBuyForm.requestSubmit(pdpBuyButton);
        return;
      } catch (_e) {
        /*
           The requestSubmit() can fail in edge cases (e.g., invalid DOM state).
           In real projects, you could:
            - In Dev/Debug: Log the error (e.g., console.error(_e)) to understand why it failed.
            - In Prod: Send it to your monitoring tool.

           We intentionally ignore it here because the fallback below guarantees the action.
         */
      }
    }

    pdpBuyButton.click();
  }
}
```

**Explanation:**

- Two static options are added:
   - `buyFormSelector`:  Selects the original buy form.
   - `stickyButtonSelector`: Selects the button inside your sticky bar.

- In the `init()` method:
   - The `_stickyButton` instance variable is used to store a reference to the sticky add-to-cart button.
   - The `click` event listener is registered to the sticky bar.
   - The private `_triggerAddToCart()` method is executed on click on the sticky bar.

- In the private `_triggerAddToCart()` method:
   - Ensures the buy form exists.
   - It checks whether the original buy button is available and enabled.
   - It attempts to submit the form using `requestSubmit()`.
      - If that fails, it falls back to triggering a `.click()` on the original buy button.

The disabled check is part of the duplicate-action protection. If the original buy button is already disabled by Shopware's add-to-cart flow, the sticky button does not trigger another submit.

<Callout title="Why No Separate Loading Indicator Here?" type="info">

In this lab, the sticky button forwards the submit to the original buy form. This keeps the loading and cart behavior owned by Shopware's existing add-to-cart flow instead of duplicating it in your custom plugin.

If you build a custom async request yourself, use a loading indicator and disable the triggering button as shown in the previous learning unit.

</Callout>

<Callout title="Reminder: The this.el" type="info">

In this storefront plugin, the `this.el` refers to the DOM element the plugin is bound to.

In this case, it is the sticky bar container `<div class="academy-sticky-add-to-cart-container fixed-top d-md-none bg-body border-bottom shadow-sm p-3 z-3" data-sticky-add-to-cart>`.

By using `this.el.querySelector('button')`, the search is scoped to this container. This ensures that only the button inside the sticky bar is selected.

</Callout>

At this point, the sticky bar is visible only on the mobile viewport and the add-to-cart action is working.

Before moving on to the styling, rebuild the storefront once more:

```bash
bin/build-storefront.sh
```

Shopware CLI equivalent:

```bash
shopware-cli project storefront-build
```

This is important when you follow the lab step by step. If you changed the storefront JavaScript code, rebuild the storefront so the compiled JavaScript in `dist` is up to date.

The last step is to add the visibility behavior and a smooth transition effect.

## Step 3: Add Styling

At this point, JavaScript already controls when the sticky bar should be shown or hidden by toggling the `is-visible` class.

Now you will use SCSS to define:

- How the sticky bar is shown and hidden.
- How the transition effect between both states is animated.

### Step 3.1: Create the SCSS Files

First, create a new SCSS file `_sticky-add-to-cart.scss`:

```bash
src/Resources/app/storefront/src/scss/component/_sticky-add-to-cart.scss
```

And the `base.scss` file:

```bash
src/Resources/app/storefront/src/scss/base.scss
```

```text
[shop_root]
 └─ custom
    └─ plugins
       └─ [your_plugin]
           ├─ src
           │  ├─ Resources
           │  │  │─ app
           │  │  │  └─ storefront
           │  │  │     │─ dist
           │  │  │     │  └─ storefront
           │  │  │     │     └─ js
           │  │  │     │        └─ ...
           │  │  │     └─ src
           │  │  │        │─ plugin
           │  │  │        │  └─ product-detail
           │  │  │        │     └─ sticky-add-to-cart.plugin.js
           │  │  │        ├─ scss // --> Create this folder
           │  │  │        │  ├─ component // --> Create this folder
           │  │  │        │  │  └─ _sticky-add-to-cart.scss // --> Create this file
           │  │  │        │  └─ base.scss // --> Create this file
           │  │  │        └─ main.js
           │  │  └─ views
           │  │     └─ storefront
           │  │        └─ page
           │  │           └─ content
           │  │              └─ product-detail.html.twig
           │  └─ [YourPluginName].php
           └─ composer.json
```

### Step 3.2: Implement the Visibility Styling and the Smooth Transition Effect

In the `_sticky-add-to-cart.scss` file, add the following code:

```scss
.academy-sticky-add-to-cart-container {
  transform: translateY(-100%);
  opacity: 0;
  transition: transform 250ms ease-out, opacity 200ms ease;
  will-change: transform, opacity;

  &.is-visible {
    transform: translateY(0);
    opacity: 1;
  }
}
```

**Explanation:**

- The `.academy-sticky-add-to-cart-container` class is used to style the sticky bar.
- By default:
   - `opacity: 0`: The element is hidden.
   - `translateY(-100%)`: The element is positioned outside the viewport.
- When the `is-visible` class is added:
  - `opacity: 1`: The element becomes visible.
  - `translateY(0)`: The element moves into the viewport.
- The `transition` property creates a smooth animation between both states.
- The `will-change` property is used to improve the performance of the transition effect.

Using `opacity` and `transform` instead of `display: none` is necessary, because the `display: none` does not allow animations.

### Step 3.3: Import the SCSS File and Verify

In the `base.scss` file, import the `_sticky-add-to-cart.scss` file:

```scss
@import "component/sticky-add-to-cart";
```

Now compile your theme and clear the cache to see the changes.

```bash
bin/console theme:compile && bin/console cache:clear
```

**Final Result:**

At this point:

- The sticky bar is only visible on mobile viewports.
- It appears when the original buy form scrolls out of view.
- The add-to-cart action is fully functional.
- The visibility change is animated smoothly.

## Optional: Compare With the Reference Implementation

If you want to validate your solution, compare your implementation with the [reference implementation](https://github.com/ShopwareAcademy/FrontendDevIntermediateStickyAddToCart).

Alternatively, you can clone the plugin in your local environment:

```bash
cd custom/plugins
git clone git@github.com:ShopwareAcademy/FrontendDevIntermediateStickyAddToCart.git
```

---

This implementation is intentionally simplified and serves as a starting point.

You can extend it further, for example:

- Add a quantity input field to the sticky bar.
- Support table viewports.
- Add further styling.

## Summary

Excellent work! You have successfully implemented the sticky add-to-cart functionality.

In this practical lab, you

- Extended the PDP template using Twig.
- Connected Twig template with a storefront JavaScript plugin via `data-` attribute.
- Implemented visibility logic based on scroll position and viewport detection.
- Reused the original buy form logic instead of duplicating cart functionality.
- Applied SCSS to handle the visibility and create smooth show/hide transitions.

With this knowledge, you can now build interactive storefront features that react to user behavior, while keeping responsibilities between Twig, SCSS, and storefront JavaScript cleanly separated.
