Pen Settings

JavaScript

Babel includes JSX processing.

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

Flutter

              
                import 'dart:convert';
import 'dart:math';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(ApiCall());

class ApiCall extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      debugShowCheckedModeBanner: false,
      theme: CupertinoThemeData(),
      home: MovieScreen(),
    );
  }
}

class MovieScreen extends StatefulWidget {
  @override
  _MovieScreenState createState() => _MovieScreenState();
}

class _MovieScreenState extends State<MovieScreen> {
  String get movie => '[ { "title": "Mission: Impossible – Fallout", "year": 2018, "image": "https://www.gstatic'
      '.com/tv/thumb/v22vodart/13492451/p13492451_v_v8_ad.jpg", "release": "July 27, 2018" }, { "title": "Incredibles'
      ' 2", "year": 2018, "image": "https://www.gstatic.com/tv/thumb/v22vodart/13446354/p13446354_v_v8_ay.jpg", "rele'
      'ase": "June 15, 2018" }, { "title": "Once Upon a Time in Hollywood", "year": 2019, "image": "https://www'
      '.gstatic.com/tv/thumb/v22vodart/15226224/p15226224_v_v8_ad.jpg", "release": "July 26, 2019" }, { "title": "Joh'
      'n Henry", "year": 2020, "image": "https://www.gstatic.com/tv/thumb/v22vodart/17733489/p17733489_v_v8_aa.jpg", '
      '"release": "January 24, 2020" }, { "title": "Timmy Failure: Mistakes Were Made", "year": 2020, "image": "https'
      '://miro.medium.com/max/500/0*7ZUeYQc4vUx_i1qQ.jpg", "release": "January 25, 2020" }, { "title": "Avengers: '
      'Endgame", "year": 2019, "image": "https://www.gstatic.com/tv/thumb/v22vodart/15366809/p15366809_v_v8_af.jpg", '
      '"release": "April 26, 2019" }, { "title": "Joker", "year": 2019, "image": "https://pbs.twimg'
      '.com/media/EDEsh0gU4AUTO3P?format=jpg&name=900x900", "release": "October 4, 2019" }, { "title": "Spider-Man: '
      'Into the Spider-Verse", "year": 2018, "image": "https://www.gstatic'
      '.com/tv/thumb/v22vodart/14939602/p14939602_v_v8_ae.jpg", "release": "December 14, 2018" }, { "title": "First '
      'Man", "year": 2018, "image": "https://www.gstatic.com/tv/thumb/v22vodart/15398283/p15398283_v_v8_ae.jpg", "rel'
      'ease": "October 10, 2018" }, { "title": "Avatar", "year": 2009, "image": "https://images-na.ssl-images-amazon.com/images/I/61jFTTf9RBL._AC_SL1230_.jpg",'
      ' "release": "December 18, 2009" } ]';

  List<Movie> _movies = [];

  void _getMovies() {
    final response = jsonDecode(movie) as List;
    setState(() => _movies = response.map((json) => Movie.toObject(json)).toList());
  }

  @override
  void initState() {
    super.initState();
    _getMovies();
  }

  @override
  Widget build(context) {
    final size = MediaQuery.of(context).size;
    return Material(
      color: Colors.white,
      child: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.only(left: 10, top: 50, bottom: 5),
              child: Text('Movies', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600)),
            ),
            Expanded(
              child: Stack(
                fit: StackFit.expand,
                children: [
                  GridView.builder(
                    padding: const EdgeInsets.only(top: 30),
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: size.width > 600 ? size.width > 900 ? 4 : 3 : 2, childAspectRatio: 0.7),
                    itemBuilder: (context, index) {
                      final movie = _movies[index];
                      final color = ColorGenerator.color;
                      return InkResponse(
                        onTap: () => Navigator.of(context).push(MaterialPageRoute(
                            builder: (_) => DetailsScreen(
                                  movie: movie,
                                  color: color,
                                ))),
                        child: Container(
                          margin: const EdgeInsets.only(left: 10, right: 10, bottom: 20),
                          decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), boxShadow: [
                            BoxShadow(
                              color: color,
                              blurRadius: 70,
                              spreadRadius: -25,
                              offset: Offset(0, 20),
                            ),
                            BoxShadow(
                              color: Colors.black.withAlpha(0x80),
                              blurRadius: 30,
                              spreadRadius: -20,
                              offset: Offset(0, 50),
                            )
                          ]),
                          child: Hero(
                            tag: 'image_${movie.title}',
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(15),
                              clipBehavior: Clip.antiAlias,
                              child: Stack(
                                fit: StackFit.expand,
                                children: [
                                  Opacity(opacity: 0.99, child: Image.network(movie.image, fit: BoxFit.cover)),
                                  Opacity(opacity: 0.6, child: Container(color: Colors.black)),
                                  Column(
                                    mainAxisSize: MainAxisSize.min,
                                    mainAxisAlignment: MainAxisAlignment.end,
                                    crossAxisAlignment: CrossAxisAlignment.start,
                                    children: [
                                      Padding(
                                        padding: const EdgeInsets.all(16),
                                        child: Text(movie.title, style: TextStyle(color: Colors.white, fontSize: 22)),
                                      )
                                    ],
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ),
                      );
                    },
                    itemCount: _movies.length,
                  ),
                  Align(
                    alignment: Alignment.topCenter,
                    child: Column(
                      children: [
                        Container(
                          height: 30,
                          decoration: BoxDecoration(
                              gradient: LinearGradient(
                            colors: [Colors.white, Colors.white.withAlpha(0x00)],
                            begin: Alignment.topCenter,
                            end: Alignment.bottomCenter,
                          )),
                        )
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsScreen extends StatefulWidget {
  final Movie movie;
  final Color color;

  const DetailsScreen({Key key, this.movie, this.color}) : super(key: key);

  @override
  _DetailsScreenState createState() => _DetailsScreenState();
}

class _DetailsScreenState extends State<DetailsScreen> {
  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final aspect = size.height * 0.75;
    return Material(
      child: SingleChildScrollView(
        child: Stack(
          children: [
            Container(
              width: size.width,
              height: size.height,
              child: BackdropFilter(
                filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40),
                child: Container(
                  width: size.width,
                  height: size.height,
                  color: widget.color.withAlpha(0x30),
                ),
              ),
            ),
            Container(
              width: size.width,
              height: size.height * 0.8,
              child: ClipPath(
                clipper: HeaderClipper(),
                child: Container(
                  color: widget.color,
                ),
              ),
            ),
            Container(
              margin: const EdgeInsets.only(right: 40),
              child: Row(
                mainAxisSize: MainAxisSize.max,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    width: aspect * 0.66,
                    height: aspect,
                    margin: EdgeInsets.only(left: 30, top: 70),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(15),
                      boxShadow: [
                        BoxShadow(
                          color: widget.color,
                          blurRadius: 300,
                          spreadRadius: 20,
                          offset: Offset(0, 100),
                        ),
                      ],
                    ),
                    child: HoverCard(
                      builder: (context, hover) {
                        return Hero(
                          tag: 'image_${widget.movie.title}',
                          child: ClipRRect(
                            borderRadius: BorderRadius.circular(15),
                            child: Image.network(widget.movie.image, fit: BoxFit.cover),
                          ),
                        );
                      },
                      depth: 0,
                      depthColor: Colors.transparent,
                      shadow: BoxShadow(
                        color: Colors.black.withAlpha(0x80),
                        blurRadius: 30,
                        spreadRadius: -20,
                        offset: Offset(0, 50),
                      ),
                    ),
                  ),
                  Expanded(
                    child: Container(
                      padding: const EdgeInsets.only(left: 30, top: 70),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(widget.movie.title,
                              style: TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.w600)),
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(15),
              child: GestureDetector(
                onTap: () => Navigator.of(context).pop(),
                child: Icon(Icons.arrow_back, size: 28, color: Colors.white),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class HeaderClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, 0);
    path.lineTo(0, size.height);
    path.quadraticBezierTo(size.width * 0.8, size.height * 0.9, size.width, size.height * 0.4);
    path.lineTo(size.width, 0);
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

class Movie {
  final String title;
  final String image;

  Movie(this.title, this.image);

  factory Movie.toObject(Map<String, dynamic> json) => Movie(json['title'], json['image']);

  Map<String, dynamic> toMap() => {'title': this.title, 'image': this.image};
}

class ColorGenerator {
  static Random random = Random();

  static Color get color => Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255));
}

class HoverCard extends StatefulWidget {
  final Widget Function(BuildContext context, bool isHovered) builder;
  final double depth;
  final Color depthColor;
  final BoxShadow shadow;
  final GestureTapCallback onTap;

  const HoverCard({
    Key key,
    @required this.builder,
    this.onTap,
    this.depth = 0,
    this.depthColor = const Color(0xFF424242),
    this.shadow = const BoxShadow(
      offset: Offset(0, 60),
      color: Color.fromARGB(120, 0, 0, 0),
      blurRadius: 22,
      spreadRadius: -20,
    ),
  }) : super(key: key);

  @override
  HoverCardState createState() => HoverCardState();
}

class HoverCardState extends State<HoverCard>
    with SingleTickerProviderStateMixin {
  double localX = 0;
  double localY = 0;
  bool defaultPosition = true;
  bool isHover = false;
  AnimationController animationController;
  Animation<FractionalOffset> animation;

  @override
  void initState() {
    super.initState();
    _setupAnimation();
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  void _setupAnimation() {
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 150),
    );
  }

  void _resetAnimation(Size size, Offset offset) {
    animationController.addListener(_updatePosition);
    animation = FractionalOffsetTween(
      begin: FractionalOffset(offset.dx, offset.dy),
      end: FractionalOffset((size.width) / 2, (size.height) / 2),
    ).animate(CurvedAnimation(
      curve: Curves.easeInOut,
      parent: animationController,
    ));
    animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        setState(() => defaultPosition = true);
        animationController.removeListener(_updatePosition);
        animationController.reverse();
      }
    });
  }

  void _updatePosition() {
    setState(() {
      localX = animation.value.dx;
      localY = animation.value.dy;
    });
  }

  void reset(Size size) {
    _resetAnimation(size, Offset(0, 0));
    _updatePosition();
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (_, dimens) {
        final size = Size(dimens.maxWidth, dimens.maxHeight);
        double percentageX = (localX / size.width) * 100;
        double percentageY = (localY / size.height) * 100;
        return Transform(
          transform: Matrix4.identity()
            ..setEntry(3, 2, 0.001)
            ..rotateX(defaultPosition ? 0 : (0.3 * (percentageY / 50) + -0.3))
            ..rotateY(defaultPosition ? 0 : (-0.3 * (percentageX / 50) + 0.3)),
          alignment: FractionalOffset.center,
          child: Container(
            width: size.width,
            height: size.height,
            decoration: BoxDecoration(
              color: widget.depthColor,
              borderRadius: BorderRadius.circular(15),
              boxShadow: [widget.shadow],
            ),
            child: GestureDetector(
              onPanUpdate: (details) {
                setState(() {
                  defaultPosition = false;
                  if (details.localPosition.dx > 0 &&
                      details.localPosition.dy > 0) {
                    if (details.localPosition.dx < size.width &&
                        details.localPosition.dy < size.height) {
                      localX = details.localPosition.dx;
                      localY = details.localPosition.dy;
                    }
                  }
                });
              },
              onPanEnd: (_) {
                setState(() {
                  isHover = true;
                  defaultPosition = false;
                });
                _resetAnimation(size, Offset(localX, localY));
                animationController.forward();
              },
              onPanCancel: () {
                setState(() {
                  isHover = false;
                });
                _resetAnimation(size, Offset(localX, localY));
                animationController.forward();
              },
              onTap: widget.onTap,
              child: MouseRegion(
                onEnter: (_) {
                  if (mounted)
                    setState(() {
                      isHover = true;
                      defaultPosition = false;
                    });
                },
                onExit: (_) {
                  if (mounted)
                    setState(() {
                      isHover = false;
                    });
                  _resetAnimation(size, Offset(localX, localY));
                  animationController.forward();
                },
                onHover: (details) {
                  RenderBox box = context.findRenderObject();
                  final _offset = box.globalToLocal(details.localPosition);
                  if (mounted)
                    setState(() {
                      defaultPosition = false;
                      if (_offset.dx > 0 && _offset.dy > 0) {
                        if (_offset.dx < size.width * 1.5 && _offset.dy > 0) {
                          localX = _offset.dx;
                          localY = _offset.dy;
                        }
                      }
                    });
                },
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(10),
                  child: Container(
                    color: widget.depthColor,
                    child: Stack(
                      children: [
                        Positioned.fill(
                          child: Transform(
                            transform: Matrix4.identity()
                              ..translate(
                                  defaultPosition
                                      ? 0.0
                                      : (widget.depth * (percentageX / 50) +
                                      -widget.depth),
                                  defaultPosition
                                      ? 0.0
                                      : (widget.depth * (percentageY / 50) +
                                      -widget.depth),
                                  0.0),
                            alignment: FractionalOffset.center,
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(10),
                              child: widget.builder(context, isHover),
                            ),
                          ),
                        ),
                        Stack(
                          children: [
                            Transform(
                              transform: Matrix4.translationValues(
                                (size.width - 50) - localX,
                                (size.height - 50) - localY,
                                0.0,
                              ),
                              child: AnimatedOpacity(
                                opacity: defaultPosition ? 0 : 0.99,
                                duration: Duration(milliseconds: 500),
                                curve: Curves.decelerate,
                                child: Container(
                                  height: 100,
                                  width: 100,
                                  decoration: BoxDecoration(boxShadow: [
                                    BoxShadow(
                                      color: Colors.white.withOpacity(0.22),
                                      blurRadius: 100,
                                      spreadRadius: 40,
                                    )
                                  ]),
                                ),
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}
              
            
!
999px

Console