zl程序教程

您现在的位置是:首页 >  Java

当前栏目

灵动岛前端Ui

2023-02-18 16:43:30 时间

一、前言

灵动岛(Dynamic Island )是什么?

灵动岛,是苹果公司iPhone 14 Pro系列 [2] 交互UI,让虚拟软件和硬件的交互变得更为流畅。当有来电、短信等通知时,灵动岛会变化它的形态,以便让用户能够更直观地接收到这些信息。而在用户使用一些应用App并将其切换到后台时(如音乐),灵动岛也能以另一种形态来显示这些软件,还可以轻点这个区域进行更复杂一点的操作,比如切换歌曲。

功能

当用户收到信息后,iPhone 14 Pro显示屏上方的灵动岛可以展开显示信息。此外灵动岛还可以显示音乐播放、Siri等组件,让用户在首页直接完成各种功能控制和信息阅读。iPhone 14 Pro 拥有6.1英寸屏幕,还将推出6.7英寸的iPhone 14 Pro MAX [2]

发展历程

2022年9月,iOS 16.1 Beta 1 发布后,苹果iPhone 14 Pro和Max灵动岛已支持单手操作。 [4]

2022年10月,媒体报道,在一次新的采访中,苹果公司软件工程高级副总裁 Craig Federighi 和苹果公司人机界面设计副总裁 Alan Dye,讨论了 iPhone 14 Pro 的灵动岛。表示 iPhone X 问世五年来的第一个重大操作变化。

我主要实现的是从灵动岛跳转csdn主页的动画效果,大家有需要可以在html部分更改跳转的链接(iframe部分)

%iframe#doom{:height => "800", :src => "https://blog.csdn.net/weixin_43233219?spm=1019.2139.3001.5343/", :width => "1200"}

tips:但是该代码的缺点是目前该跳转链接和灵动岛的大小以及Ui不匹配

二、效果展示1

三、编码实现1

html部分

#wrapper
  .phone
    .inner
      .info
        %span#time
        %span
    .island
  .framewrap
    %iframe#doom{:height => "800", :src => "https://blog.csdn.net/weixin_43233219?spm=1019.2139.3001.5343/", :width => "1200"}

css部分

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
  position: relative;
  overflow: hidden;
  background: #000;
  --transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.075);
  perspective: 2400px;
  &:after {
    content: "Enter : Start // Arrow Keys : Move // Ctrl : Shoot // Space : Open Door";
    color: #666;
    position: absolute;
    top: 10px;
    font-size: 14px;
    transition: var(--transition);
    transform: translateY(-50px);
  }
  &:before {
    content: "";
    position: absolute;
    width: 100vw;
    height: calc(50vh - 100px);
    bottom: 0;
    left: 0;
    z-index: 12;
    pointer-events: none;
    background: linear-gradient(to top, #000, rgba(0,0,0,0.01));
  }
  &.loaded {
    .phone {
      transform: translateY(0);
      filter: brightness(1);
    }
  }
  #wrapper {
    position: absolute;
    width: 100vw;
    height: 100vh;
    transition: transform 0.5s cubic-bezier(0.95, 0.84, 0.44, 1);
    transform-style: preserve-3d;
    transform: rotateX(30deg) translateZ(0px);
    transform-origin: 50% 75%;
  }
  &:hover {
    #wrapper {
      transform: rotateX(30deg) translateZ(0px) scale(1.015);
    }
  }
  &:not(.active){
    .phone .island{
      animation:pulse 10s ease-in-out infinite;
      @keyframes pulse{
        90%{
          box-shadow:0 0 0 0px rgba(0,0,0,0.01);
        }
        95%{
          box-shadow:0 0 0 3px rgba(0,0,0,0.25);
        }
        100%{
          box-shadow:0 0 0 8px rgba(0,0,0,0.01);
        }
      }
    }
  }
  &.active {
    &:after {
      transform: translateY(0);
      transition-delay: 0.5s;
    }
    #wrapper {
      transform: scale(1.25) translateZ(0px);
      transition:transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.075);
    }
    .phone {
      .island {
        &:after {
          transform: scaleX(3.5) scaleY(9.85) translateZ(0px);
          border-radius: 6px / 2px;
        }
      }
      &:before {
        filter: contrast(2);
      }
      .info {
        width: 150%;
        transition-delay: 0.05s;
      }
    }
    .framewrap {
      transform: translate(-50%, calc(-50% - 15px)) scale(1) translateZ(0px);
      iframe {
        opacity: 1;
      }
    }
  }
  .framewrap {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, calc(-50% - 15px)) scale(0) translateZ(0px);
    transform-origin: 50% calc(50% - 100px);
    width: 1000px;
    height: 210px;
    overflow: hidden;
    transition: var(--transition);
    z-index: 20;
    &:before {
      content: "";
      position: absolute;
      width: 330px;
      height: 210px;
      border-radius: 15px;
      box-shadow: inset 0 0 0 5px #000, 0 0 0 5px #000;
      z-index: 10;
      pointer-events: none;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }
  .phone {
    position: absolute;
    width: 400px;
    height: 700px;
    background: url("https://assets.codepen.io/383755/wallpaper.jpg") 50% 50% /
        cover,
      #000;
    border-radius: 50px;
    left: calc(50% - 200px);
    top: calc(50% - 160px);
    transform: scale(1.25) translateY(100vh) rotateX(-10deg);
    transition: 2s cubic-bezier(0.175, 0.885, 0.32, 1.075);
    filter: brightness(1.5);
    .inner {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      border-radius: inherit;
      overflow: hidden;
    }
    &:before {
      content: "";
      position: absolute;
      width: 100%;
      height: 100%;
      border-radius: inherit;
      left: 0;
      top: 0;
      box-shadow: inset 0 0 0 2px #666, inset 0 0 0 12.5px #000, 0 0 0 1px #fff;
      z-index: 2;
      transition: var(--transition);
    }
    &:after {
      content: "";
      position: absolute;
      width: 2px;
      height: 40px;
      background: #efefef;
      left: -3px;
      top: 125px;
      box-shadow: 0 75px 0 #efefef, 0 100px 0 #efefef;
    }
    .info {
      position: absolute;
      width: calc(100% - 100px);
      top: 25px;
      left: 50%;
      transform: translateX(-50%);
      z-index: 1;
      display: flex;
      justify-content: space-between;
      color: #fff;
      transition: 0.175s cubic-bezier(0.175, 0.885, 0.32, 1.075);
      transition-delay: 0.3s;
      span:not(#time) {
        &:before,
        &:after {
          content: "";
          position: absolute;
          top: 0;
        }
        &:before {
          width: 25px;
          height: 15px;
          right: 20px;
          background: linear-gradient(
                to right,
                #fff 3px,
                rgba(0,0,0,0.01) 3px,
                rgba(0,0,0,0.01) 10px
              )
              0px 100% / 20px 5px no-repeat,
            linear-gradient(
                to right,
                #fff 3px,
                rgba(0,0,0,0.01) 3px,
                rgba(0,0,0,0.01) 10px
              )
              5px 100% / 20px 7.5px no-repeat,
            linear-gradient(
                to right,
                #fff 3px,
                rgba(0,0,0,0.01) 3px,
                rgba(0,0,0,0.01) 10px
              )
              10px 100% / 20px 10px no-repeat,
            linear-gradient(
                to right,
                #fff 3px,
                rgba(0,0,0,0.01) 3px,
                rgba(0,0,0,0.01) 10px
              )
              15px 100% / 20px 12.5px no-repeat;
        }
        &:after {
          width: 22.5px;
          height: 13px;
          right: 0px;
          background: radial-gradient(
            circle at bottom,
            #fff 2px,
            rgba(0,0,0,0.01) 2px,
            rgba(0,0,0,0.01) 5px,
            #fff 5px,
            #fff 7px,
            rgba(0,0,0,0.01) 7px,
            rgba(0,0,0,0.01) 10px,
            #fff 10px,
            #fff 12px,
            rgba(0,0,0,0.01) 12px
          );
          clip-path: polygon(0 0, 50% 100%, 100% 0);
        }
      }
    }
    .island {
      width: 100px;
      height: 25px;
      position: absolute;
      top: 20px;
      left: 50%;
      transform: translateX(-50%);
      cursor: pointer;
      z-index: 9;
      border-radius: 50px;
      &:before,
      &:after {
        content: "";
        position: absolute;
      }
      &:before {
        width: 12.5px;
        height: 12.5px;
        border-radius: 100%;
        box-shadow: inset 0 0 2px 0.5px #333, 0 0 0 1px #111;
        top: 5px;
        right: 10px;
        z-index: 2;
        background: radial-gradient(circle at center, #222 1px, #000 1.5px) 1px
          0px / 100% 100% no-repeat;
      }
      &:after {
        width: 100%;
        height: 100%;
        border-radius: 50px;
        background: #000;
        top: 0;
        left: 0;
        transition: var(--transition);
        transform-origin: top;
      }
    }
  }
  iframe {
    clip-path: polygon(10px 207px, 650px 207px, 650px 612px, 10px 612px);
    height: 800px;
    width: 1200px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(calc(-50% + 136.5px), calc(-50% - 5px)) scale(0.5);
    opacity: 0;
    transition: var(--transition);
  }
}

js部分

document.querySelector(".island").addEventListener("click", function () {
  document.body.classList.toggle("active");
});

document.addEventListener(
  "DOMContentLoaded",
  function () {
    var today = new Date();
    var time =
      (today.getHours() % 12) +
      ":" +
      today.getMinutes().toString().padStart(2, "0");
    document.getElementById("time").appendChild(document.createTextNode(time));

    setTimeout(() => {
      document.body.classList.add("loaded");
    }, 2000);
  },
  false
);

四、效果展示2

再增加一个前端灵动岛的ui效果源码,可以 切换界面的Ui主题颜色,有官方的几个配色和随机配色 效果图如下

五、源码2

.html代码

input(name="theme" type="radio" id="deep-purple" checked)
input(name="theme" type="radio" id="gold")
input(name="theme" type="radio" id="space-black")
input(name="theme" type="radio" id="silver")
input(name="theme" type="radio" id="random")
input(name="zoom" type="checkbox" id="zoom")


.scene

	.pallette
		label.swatch(tabIndex="0" for="deep-purple")
		label.swatch(tabIndex="0" for="gold")
		label.swatch(tabIndex="0" for="space-black")
		label.swatch(tabIndex="0" for="silver")
		label.swatch(tabIndex="0" for="random")
			.icon
			.tooltip CMD + click to save
		
	.zoom-con
		label.swatch(tabIndex="0" for="zoom")
	
	.phone-con
		.phone
			.buttons
				.left
					.button
					.button
					.button
				.right
					.button
			.camera
			.screen-container
				.bg
					.deep-purple
						.section
							.glow
						.section
							.glow
					.gold
						.section
							.glow
						.section
							.glow
					.space-black
						.section
							.glow
						.section
							.glow
					.silver
						.section
							.glow
						.section
							.glow
					.random.canvas
						.shapes
							.shape
							.shape
							.shape
							.shape
							.shape
							.shape
				.notch-container(tabIndex="0")
					.notch
						.content
							.left
								.tile
								.text
							.right
							.bar
								.duration
				.notch-blur
				.screen
					.app
						.weather
					.app
					.app
					.app
					.app
					.app
					.app
					.app
					.app
					.app

.css代码

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400&display=swap');

:root {
	--size: max(5px, 1vmin);
	--height: 80em;
	--pad: 1.25em;
	--border-radius: 6.666em;
	--gutter: calc(var(--pad) * 2);
	--scene-pad: 5vmin;
	--bg-blur: 0.333em;
	
	--button-width: 0.333em;
	
	--notch-height: 3.33em;
	--notch-width: 33.3%;
	--notch-radius: calc(var(--border-radius) - calc(var(--pad) * 2));
	--notch-duration: 0.333s;
	
	--ease: cubic-bezier(.666, 0, .4, 1);
	--ease-spring: cubic-bezier(.666, 0, .4, 1.2);
	--ease-out: cubic-bezier(.15,0,.333,1);
	
	--border-width: 0.4em;
	
	--deep-purple: 284;
	--gold: 22.5;
	--space-black: 215;
	--silver: 254;
	
	--c-h: var(--deep-purple);
	--c-s: 100%;
	--c-l: 50%;
}

// Squircle effect
@function round-off() {
	@return url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1'><defs><filter id='round'><feGaussianBlur in='SourceGraphic' stdDeviation='5' result='blur' /><feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9' result='goo'/><feComposite in='SourceGraphic' in2='goo' operator='atop'/></filter></defs></svg>#round");
}

// Smoother gradients
@function scrim-gradient(
	$startColor,
	$direction: 'to bottom',
	$from: 0,
	$to: 100,
	$type: linear,
	$reverse: false,
	$debug: false
) {
	$scrimCoordinates: (
		0: 1,
		19: 0.738,
		34: 0.541,
		47: 0.382,
		56.5: 0.278,
		65: 0.194,
		73: 0.126,
		80.2: 0.075,
		86.1: 0.042,
		91: 0.021,
		95.2: 0.008,
		98.2: 0.002,
		100: 0
	);
	
	$hsl: '';
	
	@if type-of($startColor) == 'list' {
		$hsl: $startColor;
	} @else {
		$hue: hue($startColor);
		$saturation: saturation($startColor);
		$lightness: lightness($startColor);
		$hsl: #{$hue}, #{$saturation}, #{$lightness};
	}

	$stops: ();

	@each $colorStop, $alpha in $scrimCoordinates {
		$stop:
			unquote("hsla(#{$hsl}, #{if($reverse, 1 - $alpha, $alpha)})") 
			$from + ($colorStop/($to/(100 - $from))) * 1%;
		$stops: append($stops, $stop, comma);
	}

	@if $debug {
		@return '$startColor: #{$startColor},$direction: #{$direction}, $from: #{$from}, $to: #{$to}, $type: #{$type}, $reverse: #{$reverse}';
	} 
	
	@else {
		@return #{$type}-gradient(unquote($direction), $stops);
	}

}

@keyframes appear {
	to {
		transform: scale3d(1,1,1);
		opacity: 1;
	}
}

body {
	background: darken(blue, 48);
}

.scene {
	display: flex;
	flex-wrap: wrap;
	gap: 3em 0;
	align-items: center;
	align-content: center;
	justify-content: center;
	min-height: 100vh;
	font-family: Inter;
	font-size: var(--size);
	padding: var(--scene-pad);
	box-sizing: border-box;
}

.phone-con {
	flex-basis: 100%;
	display: flex;
	justify-content: center;
}

.phone {
	position: relative;
	z-index: 1;
	
	aspect-ratio: 37/76;
	background: black;
	height: var(--height);
	border-radius: var(--border-radius);
	box-shadow: 0 0 0.1em 0.25em hsl(var(--c-h), 20%, 25%), 0 0 0 var(--border-width) hsl(var(--c-h), 30%, 85%);
	box-sizing: border-box;
	
	opacity: 0;
	transform: scale3d(1.1,1.1,1);
	animation: appear 1s var(--ease-out) forwards;
	webkit-backface-visibility: hidden;
	
	&:before {
		content: '';
		position: absolute;
		top: var(--border-radius);
		right: calc(var(--border-width) * -1);
		bottom: var(--border-radius);
		left: calc(var(--border-width) * -1);
		border: 0.5em solid hsl(var(--c-h), 20%, 30%);
		border-left-width: 0;
		border-right-width: 0;
	}
}

.buttons {
	position: absolute;
	inset: calc(var(--border-width) * -1);
	pointer-events: none;
	
	.left,
	.right {
		position: absolute;
		width: var(--button-width);
		display: flex;
		flex-direction: column;
		align-items: stretch;
		gap: 1.5em;
	}
	
	.left {
		right: 100%;
		top: calc(var(--border-radius) * 2);
		
		.button {
			
			&:nth-child(1) {
				height: 3em;
				margin-bottom: 0.5em;
			}
		}
	}
	
	.right {
		left: 100%;
		transform: scale3d(-1, 1, 1);
		top: calc(var(--border-radius) * 3);
		
		.button {
			height: 9.5em;
		}
	}
	
	.button {
		background: hsl(var(--c-h), 20%, 95%);
		height: 6em;
		box-shadow:
		 	inset -0.15em 0 0.1em black, 
			inset 0 0 0.1em hsl(var(--c-h), 30%, 90%),
			inset 0 0.2em 0.1em hsl(var(--c-h), 30%, 90%),
			inset 0 -0.2em 0.1em hsl(var(--c-h), 30%, 90%),
		 	inset -0.1em 0.333em 0.1em rgba(black, 0.5), 
		 	inset -0.1em -0.333em 0.1em rgba(black, 0.5), 
		;
		
		border-top-left-radius: 0.2em;
		border-bottom-left-radius: 0.2em;
	}
}

.screen-container {
	position: absolute;
	// overflow: hidden;
	inset: 0;
	
	border-radius: var(--border-radius);
	border: var(--pad) solid black;
	
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: calc(var(--pad) * 2);
	
	// Bottom thingy
	&:before {
		content: '';
		position: absolute;
		z-index: 2;
		background: white;
		width: 36.6%;
		bottom: calc(var(--pad) * 0.75);
		height: calc(var(--pad) * 0.5);
		border-radius: calc(var(--pad) * 0.25);
		filter: drop-shadow(0 0.1em 0.25em rgba(black, 0.1));
	}
}

.bg {
	position: absolute;
	inset: 0;
	background: black;
	border-radius: calc(var(--border-radius) - var(--pad));
	overflow: hidden;
	// filter: round-off();
	transform: translateZ(0);
	
	> * {
		position: absolute;
		inset: 0;
		display: flex;
		flex-direction: column;
		opacity: 0;
		transition: opacity 1s var(--ease-out) 0.5s;
		background: black;
	}
	
	.section {
		--g-h: var(--c-h);
		--g-s: var(--c-s);
		--g-l: var(--c-l);
		
		flex-grow: 1;
		position: relative;
		overflow: hidden;
		
		border-radius: calc(var(--border-radius) - var(--pad));
		border-bottom-left-radius: 20em;
		border-bottom-right-radius: 20em;
		
		&:before {
			content: '';
			position: absolute;
			inset: 0;
			border-radius: inherit;
			
			background:
				scrim-gradient(black, '120% 110% at 50% 92.5%', $from: 33.3, $type: radial),
				scrim-gradient((calc(var(--g-h) - var(--g-hue-adjust-2, var(--g-hue-adjust))), 100%, 50%), '100% 66.6% at 110% var(--g-hue-adjust-2-y, 100%)', $from: 33.3, $type: radial),
				scrim-gradient((calc(var(--g-h) - var(--g-hue-adjust-2, var(--g-hue-adjust))), 100%, 50%), '100% 66.6% at -10% var(--g-hue-adjust-2-y, 100%)', $from: 33.3, $type: radial),
				scrim-gradient((calc(var(--g-h) + 33.3), 100%, var(--g-lightness, 82.5%)), '150% 100% at 50% 80%', $from: 35, $type: radial, $reverse: true),
			;
			
			background-color: hsl(var(--g-h), var(--g-s), var(--g-l));
			
			transform: scale3d(1.1, 1.25, 1);
			transform-origin: bottom;
			transition: transform 1s var(--ease-out) 0.5s;
		}
		
		&:after {
			content: '';
			position: absolute;
			inset: 0;
			border: var(--border-width) solid rgba(white, 0.8);
			border-radius: inherit;
			filter: blur(0.05em);
			// backdrop-filter: blur(0.75em);
			
			mask-image: radial-gradient(100% 100% at 50% 70%, black 30%, transparent 50%);
			transform: translatez(2px);
		}
		
		.glow {
			position: absolute;
			inset: 0;
			border-radius: inherit;
			mix-blend-mode: overlay;
			z-index: 1;
			background: radial-gradient(80% 150% at 50% 100%, hsl(var(--g-h), 100%, var(--g-l)), transparent 70%);
		}
	}
	
	// Bottom bg section
	.section:last-of-type {
		--g-h: calc(var(--c-h) - var(--g-hue-adjust, 0));
		--g-l: calc(var(--c-l) + 40%);
		--g-lightness: 95%;
		
		transform: scale3d(1, -1, 1) translateZ(1px);
	}
}

.notch-container {
	position: absolute;
	z-index: 3;
	top: var(--pad);
	right: var(--pad);
	left: var(--pad);
	display: flex;
	justify-content: center;
	
	height: 100%;
	max-height: calc(var(--notch-radius) * 2);
	
	pointer-events: none;
	outline: none;
		
	transition: var(--notch-duration) var(--ease);
	transition-property: max-height, max-width, filter, transform;
	will-change: max-width, max-height, filter;
	
	// Stop transitions when resizing screen or zooming
	.is-resizing &,
	.is-resizing & * {
		transition: none;
	}
	
	&:hover,
	&:focus-within {
		--shadow-opacity: 0.5;
		transition-timing-function: var(--ease-spring);
		
		.content {
			--content-padding: 2em;
			
			.text {
				opacity: 1;
			}
		}
		
		.notch {
			max-width: 100%;
			max-height: 100%;
			// transition: max-width 0.3s ease-in-out, max-height 0.2s ease-in-out 0.1s;
			pointer-events: all;
			transform: scale3d(1,1,1);
		}
		
		~ .notch-blur {
			opacity: 1;
			max-height: calc(var(--notch-radius) * 3.333 + var(--pad));
		}
		
	}
	
	&:focus-within {
		max-height: calc(var(--notch-radius) * 3);
		
		--bar-height: 1em;
		--bar-opacity: 1;
		
		.left,
		.right {
			max-height: calc(100% - var(--bar-height, 0%) - var(--content-gap));
		}
					
		~ .notch-blur {
			max-height: calc(var(--notch-radius) * 5);
			opacity: 1;
		}
	}
}

// Blurred drop-shadow
.notch-blur {
	position: absolute;
	z-index: 2;
	top: calc(var(--pad) - 3px);
	right: calc(var(--pad) - 3px);
	left: calc(var(--pad) - 3px);
	height: 100%;
	max-height: calc(var(--notch-radius) * 1.5);
	backdrop-filter: blur(0.2em);
	mask-image: scrim-gradient(black, $from: 60);
	opacity: 0;
	
	border-radius: calc(var(--border-radius) - var(--pad));
	
	transition: var(--notch-duration) var(--ease);
	transition-property: max-height, max-width, opacity, transform;
	will-change: max-width, max-height;
}

.notch {
	position: relative;
	border-radius: var(--notch-radius);
	pointer-events: all;
	overflow: hidden;
	color: white;
	
	// max-height: var(--notch-height);
	// max-width: var(--notch-width);

	display: flex;
	
	cursor: pointer;
	
	width: 100%;
	// max-width: var(--notch-width);
	
	transition: inherit;
	transition-property: inherit;
	will-change: inherit;
	
	filter: drop-shadow(0 1em 2em hsla(0 0% 0% / var(--shadow-opacity, 0)));
	
	transform: scale3d(0.375,0.4,1);
	transform-origin: top;

	&:before {
		content: '';
		position: absolute;
		inset: 0;
		background: black;
		filter: round-off();
		border-radius: inherit;
	}
}

.content {
	--content-padding: 1.75em;
	--duration-height: 0.5em;
	--content-gap: 1em;
	
	width: 100%;
	display: flex;
	flex-wrap: wrap;
	align-items: stretch;
	justify-content: stretch;
	padding: var(--content-padding);
	gap: var(--content-gap);
	font-size: 125%;
	
	transition-property: padding;
	will-change: padding;
	
	position: relative;
	
	.left,
	.right {
		height: 100%;
		max-height: calc(100% - var(--bar-height, 0%));
		
		display: flex;
		align-items: center;
		gap: 1em;
		
	}
	
	&, .left, .right, .bar, .text {
		transition: var(--notch-duration) var(--ease-out);
	}
	
	.left, .right, .bar {
		transition-property: max-height, opacity;
		will-change: max-height;
	}
	
	.left {
		flex-grow: 2;
		// background: red;
	}
	
	.text {
		display: flex;
		flex-direction: column;
		gap: 0.333em;
		transition-property: opacity;
		
		opacity: var(--bar-opacity, 0);
		
		&:before {
			content: 'The Move';
			order: 1;
			text-transform: uppercase;
		}

		&:after {
			order: 2;
			content: 'Space Rangers';
			opacity: 0.5;
		}
	}
	
	.right {
		flex-grow: 1;
		// background: blue;
	}
	
	.tile {
		background: #{desaturate(darken(blue, 10), 25%)};
		height: 100%;
		aspect-ratio: 1;
		border-radius: 33.3%;
		position: relative;
		
		&:before {
			content: '';
			position: absolute;
			inset: 0;
			border-radius: 50%;
			
			background: radial-gradient(50% 50% at 55% 40%, desaturate(darken(blue, 20), 25%), #cd1385 75%, transparent) center / 133.3% 133.3%;
			background-color: yellow;
			filter: blur(0.05em);
		}
	}
	
	.bar {
		display: flex;
		align-items: center;
		gap: 1em;
		flex-basis: 100%;
		height: 100%;
		max-height: var(--bar-height, 0%);
		color: rgba(white, 0.5);
		
		opacity: var(--bar-opacity, 0);
		
		.duration {
			position: relative;
			height: var(--duration-height);
			background: rgba(white, 0.25);
			border-radius: calc(var(--duration-height) * 0.5);
			overflow: hidden;
			flex-grow: 1;
			
			&:before {
				content: '';
				height: 100%;
				background: white;
				width: 25%;
				position: absolute;
			}
		}
			
		&:before {
			content: '1:20';
		}
		
		&:after {
			content: '-1:48';
		}
	}
}

.camera {
	display: flex;
	justify-content: center;
	align-items: center;
	height: var(--notch-height);
	aspect-ratio: 1/1;
	border-radius: 50%;
	pointer-events: none;
	
	position: absolute;
	z-index: 4;
	top: calc(var(--pad) * 2);
	right: calc(50% - calc(var(--notch-width) * 0.5));
	margin-right: calc(var(--pad) * 0.333);

	&:before {
		content: "";
		height: 33.3%;
		aspect-ratio: 1;
		border-radius: inherit;
		box-shadow:
			inset 0 0 0.25em #4c4da3
		;
		background:
			radial-gradient(#6667ac, transparent 50%) no-repeat 33.3% 10% / 75% 50%,
			radial-gradient(darken(#6667ac, 15), transparent 50%) no-repeat 60% 85% / 50% 50%,
			;
		background-color: #080928;
	}
}

.screen {
	// opacity: 0;
	display: flex;
	flex-wrap: wrap;
	align-content: flex-start;
	flex-grow: 1;
	gap: var(--gutter);
	
	box-sizing: border-box;
	width: 100%;
	position: relative;
	overflow: hidden;
	z-index: 1;
	
	padding: var(--gutter);
	padding-top: calc(var(--gutter) * 3);
	border-radius: calc(var(--border-radius) - var(--pad));
	
	transition: opacity 1s var(--ease-out) 0.25s;
}

.app {
	--col: 4;
	
	aspect-ratio: 1;
	border-radius: 20%;
	flex-basis: 15%;
	flex-grow: 1;
	
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 2%;
	
	padding: 5%;
	box-sizing: border-box;
	font-size: 1.5em;
	
	--scale: 1.5;
	--duration: 0.8s;
	
	transform: scale3d(var(--scale), var(--scale), 1);
	animation: appear var(--duration) var(--ease-out) forwards;

	&:nth-child(1),
	&:nth-child(2) {
		flex-basis: 40%;
		border-radius: 15%;
		background: linear-gradient(190deg, var(--app-bg-s1, white) 33.3%, var(--app-bg-s2, var(--app-bg-s1, white))) top / 100% 125%;
	}
	
	&:not(:nth-child(1)):not(:nth-child(2)) {
		&:before {
			content: '';
			background: linear-gradient(190deg, var(--app-bg-s1, white), var(--app-bg-s2, var(--app-bg-s1, white))) top / 100% 125%;
			border-radius: inherit;
			position: absolute;
			inset: 0;
			filter: round-off();
		}
	}
	
	&:nth-child(1),
	&:nth-child(2),
	&:nth-child(3),
	&:nth-child(6),
	&:nth-child(7),
	&:nth-child(10) {
		--scale: 1.75;
		--duration: 1s;
	}
		
	&:nth-child(1) {
		--app-bg-s1: #{darken(#6490d2, 10)};
		--app-bg-s2: #6490d2;
		
		color: white;
		transform-origin: 125% 200%;
		
		&:after {
			content: 'Sunny';
		}
		
		.weather {
			display: flex;
			flex-direction: column;
			gap: 2%;
			
			&:before {
				content: 'Oakland';
			}
			
			&:after {
			font-size: 225%;
				content: '80°';
			}
		}
	}
		
	&:nth-child(2) {
		--app-bg-s1: #cbe2ae;
		--app-bg-s2: #{lighten(#cbe2ae, 15)};
		
		transform-origin: -25% 200%;
		
		&:before {
			content: '';
			align-self: flex-end;
			width: 40%;
			background: #f8d7a2;
			border: var(--border-width) solid white;
			aspect-ratio: 1;
			border-radius: 50%;
		}
		
		&:after {
			content: 'Hudson Ave';
			font-weight: 500;
			margin-top: auto;
		}
	}
	
	&:nth-child(3) {
		--app-bg-s1: #a7f88f;
		--app-bg-s2: #41c144;
		
		transform-origin: 175% 200%;
	}
	
	&:nth-child(4) {
		transform-origin: 75% 230%;
	}
	
	&:nth-child(5) {
		--app-bg-s1: #cecdd5;
		--app-bg-s2: #89888d;
		
		transform-origin: -25% 230%;
	}
		
	&:nth-child(6) {
		--app-bg-s1: #1ac5fb;
		--app-bg-s2: #1d71f2;
		
		transform-origin: -125% 200%;
	}
		
	&:nth-child(7) {
		--app-bg-s1: #fe9b01;
		--app-bg-s2: #f67324;
		
		transform-origin: 175% 80%;
	}
	
	&:nth-child(8) {
		--app-bg-s1: #cb65f0;
		--app-bg-s2: #8628bb;
		
		transform-origin: 75% 100%;
	}
	
	&:nth-child(9) {
		--app-bg-s1: #1d71f2;
		--app-bg-s2: #1ac8fd;
		
		transform-origin: -25% 100%;
	}
		
	&:nth-child(10) {
		
		transform-origin: -125% 80%;
	}
}

.pallette {
	position: relative;
	z-index: 1;
	
	order: 1;
	display: flex;
	gap: 2em;
	margin-right: 2.25em;
	margin-bottom: -.25em;
	
	&:hover ~ .phone-con .screen {
		transition-delay: 0.5s;
		opacity: 0;
	}
}

.zoom-con {
	order: 1;
}

// --------------
// Theme picker
// --------------

// Hide inputs
input[type="radio"],
input[type="checkbox"] {
	display: none;
}

.swatch {
	--swatch-size: max(30px,5em);
	--border-opacity: 0;
	
	background: white;
	width: var(--swatch-size);
	height: var(--swatch-size);
	border-radius: 50%;
	position: relative;
	cursor: pointer;
	
	border: max(1.5px, var(--border-width)) solid black;
	box-sizing: border-box;
	// transform: scale3d(0.85, 0.85, 1);
	
	background: radial-gradient(100% 100% at 50% 30%, var(--swatch-s1), var(--swatch-s2));
	
	opacity: 0;
	transform: translate3d(0, 20%, 0) scale3d(0.75, 0.75, 1);
	animation: appear 0.333s var(--ease-out) forwards 0.75s;
	
	display: flex;
	align-items: center;
	color: white;
	justify-content: center;
	
	outline: none;
	
	&:before {
		content: '';
		position: absolute;
		inset: -1px;
		border-radius: inherit;
		border: var(--border-width) solid black;
		background-image: radial-gradient(400% 300% at 50% 225%, transparent 20%, white);
		// filter: blur(0.2em);
		// box-shadow: inset 0 0 0.1em 0.1em black;
	}
	
	&:not([for="zoom"]) {
		
		&:after {			
			content: '';
			position: absolute;
			inset: calc(var(--border-width) * -1);
			border-width: inherit;
			border-style: inherit;
			opacity: var(--border-opacity);
			transition: 0.25s var(--ease-out);
			transition-property: opacity;
			border-radius: inherit;
		}
		
		&:hover {
			--border-opacity: 0.666;
		}
		
		&:focus {
			--border-opacity: 1;
		}
	}
	
	&:nth-child(1) {
		transform-origin: 100% 200%;
	}
		
	&:nth-child(2) {
		animation-delay: 0.8s;
		transform-origin: 80% 200%;
	}
	
	&:nth-child(3) {
		animation-delay: 0.85s;
		transform-origin: 50% 200%;
	}
			
	&:nth-child(4) {
		animation-delay: 0.9s;
		transform-origin: 40% 200%;
	}
				
	&:nth-child(5) {
		animation-delay: 0.95s;
		transform-origin: 20% 200%;
	}
	
	&[for="deep-purple"] {
		--swatch-s1: hsl(var(--deep-purple), 100%, 50%);
		--swatch-s2: hsl(calc(var(--deep-purple) - 60), 100%, 20%);
		
		&:before {
			opacity: 0.5;
		}
	}
		
	&[for="gold"] {
		--swatch-s1: hsl(var(--gold), 100%, 50%);
		--swatch-s2: hsl(var(--gold), 100%, 50%);
	}
	
	&[for="space-black"] {
		--swatch-s1: hsl(var(--space-black), 50%, 20%);
		--swatch-s2: hsl(var(--space-black), 50%, 10%);
		
		&:before {
			opacity: 0.5;
		}
	}
	
	&[for="silver"] {
		--swatch-s1: hsl(var(--silver), 50%, 80%);
		--swatch-s2: hsl(var(--silver), 50%, 10%);
		
		&:before {
			opacity: 0.75;
		}
	}
	
	&[for="random"] {
		
		&:before {
			background: none;
		}
		
		border-color: rgba(white, 0.25);
		transition: border-color 0.2s var(--ease-out);
		
		&:hover {		
			border-color: rgba(white, 0.75);
			
			.icon {
				opacity: 1;
			}
		}
		
		.icon {
			position: absolute;
			inset: 22.5%;
			background: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m18 15 3 3m0 0-3 3m3-3h-4l-1-1-1-1h-1m4-13 3 3m0 0-3 3m3-3h-4l-1 1-1 1-6 8-1 1-1 1H3M3 6h4l1 1 1 1 1 1' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain; 
			opacity: 0.75;
			transition: opacity 0.2s var(--ease-out);
		}
		
		#random:not(:checked) ~ .scene &:after {
			border: none;
		}
		
		#random:checked ~ .scene & {
			--swatch-s1: hsl(var(--r-h), var(--r-s), var(--r-l));
			--swatch-s2: hsl(var(--r-h), var(--r-s), calc(min(98%, var(--r-l) + 10%)));
			
			&:after {
				border-color: hsl(var(--r-h), 50%, 75%);
			}
			
			.icon {
				background: url("data:image/svg+xml,%3Csvg width='24' height='24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m18 15 3 3m0 0-3 3m3-3h-4l-1-1-1-1h-1m4-13 3 3m0 0-3 3m3-3h-4l-1 1-1 1-6 8-1 1-1 1H3M3 6h4l1 1 1 1 1 1' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain; 
			}
			
			&:hover,
			&:focus {
				.tooltip {
					pointer-events: all;
					opacity: 1;
				}
			}
		}		
	}

	&[for="zoom"] {
		--zoom-icon: url("data:image/svg+xml,%3Csvg width='32' height='32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m5 27 9-9m-9 9v-9m0 9h9M27 5l-9 9m9-9v9m0-9h-9' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
		animation-delay: 1s;
		transform-origin: 40% 200%;

		border-color: rgba(white, 0.25);
		transition: border-color 0.2s var(--ease-out);

		&:before {
			background: none;
		}

		&:after {
			content: '';
			position: absolute;
			inset: 22.5%;
			background: var(--zoom-icon) center / cover;
			opacity: 0.75;
			transition: opacity 0.2s var(--ease-out);
		}

		&:hover {		
			border-color: rgba(white, 0.5);

			&:after {
				opacity: 1;
			}
		}

		&:focus {		
			border-color: rgba(white, 0.75);

			&:after {
				opacity: 1;
			}
		}
	}
}

// Deep Purple theme
.deep-purple {
	--c-h: var(--deep-purple);
	--g-hue-adjust: 40;
	--g-hue-adjust-2: 45;
	
	.section:last-of-type {
		--g-hue-adjust-2-y: 85%;
	}
}

// Gold theme
.gold {
	--c-h: var(--gold);
	--g-hue-adjust: 0;
	
	.section:last-of-type {
		--g-hue-adjust: 15;
		--g-hue-adjust-2: -190;
		--g-lightness: 82.5%;
	}
}

// Space Black theme
.space-black {
	--c-h: var(--space-black);
	--g-hue-adjust: 0;
	--g-lightness: 95%;
	
	.section:last-of-type {
		--g-hue-adjust: 215;
		--g-hue-adjust-2: -215;
	}
}

// Silver theme
.silver {
	--c-h: var(--silver);
	--c-s: 10%;
	--g-hue-adjust: 40;
	--g-lightness: 95%;
	--g-hue-adjust-2-y: 85%;
	
	.section:last-of-type {
		--g-hue-adjust-2: 15;
	}
			
	.glow {
		opacity: 0.25;
	}
}

$themes: 'deep-purple', 'gold', 'space-black', 'silver', 'random';

@each $theme in $themes {
	
	label[for="#{$theme}"]:after {
		border-color: hsl(var(--#{$theme}), 50%, 75%);
	}
	
	##{$theme}:checked {
		
		~ .scene .phone .bg .#{$theme} {
			opacity: 1;
			z-index: 1;
			transition-delay: 0s;
			
			.section:before {
				transition-delay: 0s;
				transform: scale3d(1,1,1) translate3d(0, 0 ,0);
			}
		}
		
		~ .scene .pallette label[for="#{$theme}"] {
			--border-opacity: 1 !important;
		}
	}
}

// Zoom
#zoom:checked ~ .scene {
	--size: max(5px, 2vmin);
		
	opacity: 0;
	animation: appear 0.5s var(--ease-out) forwards;
	
	.phone {
		order: 1;
	}
	
	.pallette,
	.zoom-con {
		order: 0;
		font-size: 50%;
	}

	label[for="zoom"] {
		--zoom-icon: url("data:image/svg+xml,%3Csvg width='32' height='32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m14 18-9 9m9-9v9m0-9H5M18 14l9-9m-9 9V5m0 9h9' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
	}
}

#zoom:not(:checked) ~ .scene {
	opacity: 0;
	animation: appear-again 0.5s var(--ease-out) forwards;
	
	@keyframes appear-again {
		to {
			transform: scale3d(1,1,1);
			opacity: 1;
		}
	}
}


// Random gradient
.random {
	background: hsl(var(--r-h), var(--r-s), var(--r-l));
	
	&--colourful {
		--bm-outer: overlay;
		--bm-inner: difference;
	}
	
	&--moody {
		--bm-outer: exclusion;
		--bm-inner: multiply;
	}
	
	&--neon {
		--bm-outer: hue;
		--bm-inner: multiply;
	}
		
	&--abstract {
		--bm-outer: color-burn;
		--bm-inner: difference;
	}
	
	&--grayscale {
		--bm-outer: color-burn;
		--bm-inner: difference;
		filter: brightness(1.2) grayscale(100%) contrast(2);
	}
	
	&--light-leak {
		--bm-outer: color-burn;
		--bm-inner: multiply;
	}
}

.shapes {
	mix-blend-mode: var(--bm-outer, overlay);
	background: hsl(var(--r-h), var(--r-s), 85%);
	position: absolute;
	inset: 0%;
	z-index: 1;
	backface-visibility: hidden;
}

.shape {
	position: absolute;
	pointer-events: none;
	transition-property: background, inset, border-radius, filter, transform;
	will-change: inset, border-radius, filter;
	mix-blend-mode: var(--bm-inner, difference);
	top: 50%;
	left: 50%;
	aspect-ratio: 1/1;
	
	background: hsl(var(--r-h), var(--r-s), var(--r-l));
	width: var(--w, 99%);
	border-radius: var(--b-r, 55%);
	filter: blur(var(--b));
	transform: translate3d(calc(var(--x, -1%) - 50%), calc(var(--y, -8%) - 50%), 0) scale3d(var(--s-x, 1.9), var(--s-y, 1.15), 1) rotate(var(--r, 260deg));
}

.tooltip {
	--t-caret: 0.5em;
	
	position: absolute;
	z-index: 1;
	top: calc(100% + var(--t-caret) + 0.5em);
	white-space: nowrap;
 	background: black;
	color: rgba(white, 0.75);
	padding: 0.25em;
	font-size: max(11px, 1em);
	border-radius: 0.25em;
	border: 1px solid lighten(black, 25);
	
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.25s var(--ease);
	
	&:after {
		content: '';
		position: absolute;
		bottom: calc(100% - calc(var(--t-caret) / 2) - 1px);
		left: calc(50% - calc(var(--t-caret) / 2));
		border-radius: inherit;
		height: var(--t-caret);
		width: var(--t-caret);
		background: inherit;
		border: inherit;
		border-top-color: transparent;
		border-left-color: transparent;
		transform: rotate(-135deg);
	}
	
	#zoom:checked ~ .scene & {
		top: unset;
		bottom: calc(100% + var(--t-caret) + 0.5em);
		
		&:after {
			transform: rotate(45deg);
			top: calc(100% - calc(var(--t-caret) / 2) - 1px);
			bottom: unset;
		}
	}
}

js部分

// Tiny bit of JS to ensure that the notch doesn't move about when you resize the screen


const delay = 300;
let afterResize;
let currentStyle;

window.onresize = function(){
	document.body.classList.add('is-resizing');
  	clearTimeout(afterResize);
  	afterResize = setTimeout(() => document.body.classList.remove('is-resizing'), delay);
};

document.getElementById('zoom').addEventListener('click', () => {
	document.body.classList.add('is-resizing');
	setTimeout(() => 		document.body.classList.remove('is-resizing'), delay)
});


// Generating random gradient

let dimension = 1000; // Size of tile to be download px

const styles = ['colourful', 'moody', 'neon', 'abstract', 'grayscale', 'light-leak'];

const generateBtn = document.querySelector('[for="random"]');
const canvas = document.querySelector('.canvas');

const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);

// Generate randomised gradients

generateBtn.addEventListener('click', generateOrSave);

function generateOrSave(e) {
	
	if (e.metaKey) {
		saveGradient(e);
	} else {
		generateGradient();
	}
}

function generateGradient() {
	
	// Assign style
	const newStyle = styles[Math.floor(Math.random() * styles.length)];
	
	// console.log(styles, styles[Math.floor(Math.random() * styles.length)]);
	
	if (currentStyle) canvas.classList.remove(`random--${currentStyle}`);
	
	currentStyle = newStyle;
	canvas.classList.add(`random--${newStyle}`);
	
	// Loop through each canvas and assign a bunch of random CSS variables
	const shapes = canvas.getElementsByClassName('shape');

	document.body.style.setProperty('--r-h', `${random(0, 360)}deg`);
	document.body.style.setProperty('--r-s', `${random(40, 90)}%`);
	document.body.style.setProperty('--r-l', `${random(55, 90)}%`);

	Object.values(shapes).forEach((shape) => {
		shape.style.setProperty('--r-h', `${random(0, 360)}deg`);
		shape.style.setProperty('--r-s', `${random(40, 90)}%`);
		shape.style.setProperty('--r-l', `${random(55, 90)}%`);

		shape.style.setProperty('--w', `${random(0, 30) + 85}%`);
		shape.style.setProperty('--b-r', `${random(20, 60)}%`);
		shape.style.setProperty('--b', `${random(5, 75) / 10}em`);
		shape.style.setProperty('--x', `${random(0, 100) - 50}%`);
		shape.style.setProperty('--y', `${random(0, 100) - 50}%`);
		shape.style.setProperty('--s-x', `${1 + ((random(0, 130) - 30) / 100)}`);
		shape.style.setProperty('--s-y', `${1 + ((random(0, 130) - 30) / 100)}`);
		shape.style.setProperty('--r', `${random(0, 720) - 360}deg`);
	})
}


// Convert RGB colour to Hex
// Needed for api.color.pizza call
const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`


// Save gradient
function saveGradient(e) {
	const gradient = canvas;
	const rect = gradient.getBoundingClientRect();
	const scale = dimension / rect.width

	//	Get canvas background color
	const color = rgba2hex(window.getComputedStyle(gradient, null).getPropertyValue('background-color'));
	
	console.log(color, scale, rect);
	
	// Get name of color for use in file name
	fetch(`https://api.color.pizza/v1/${color.substring(1)}`)
		.then(c => c.json())
		.then(c => {
			// console.log(c);
		
			// Convert DOM to canvas
			domtoimage.toPng(gradient, {
				bgColor: '#ffffff',
				width: rect.width * scale,
				height: rect.height * scale,
				style: {
					  'transform': `scale(${scale})`,
					  'transform-origin': 'top left'
				 }
			})
			// Download image
			.then(function (dataUrl) {
				// const img = new Image();
				// img.src = dataUrl;
				// document.body.appendChild(img);
				
				// Render canvas as a link and click dat
				const link = document.createElement('a');
				link.download = `${currentStyle}-${c['paletteTitle'].toLowerCase().replaceAll(' ','-')}-gradient`;
				link.href = dataUrl;
				link.click();
			})
	});
	
}