|
/* |
|
* 4 February 2022 |
|
* CSS No DIV Geometric Blooming Flower π» |
|
* inspired by Robin Davey's artwork π tmblr.co/Zm4Rmx2Nv6bXq |
|
* based on the concept of my SVG-in-CSS Flipping Checkerboard π https://codepen.io/ShadowShahriar/pen/podoZKr |
|
|
|
* made this CodePen for my best friend β€ |
|
* thank you for all the love and support you have given me since the beginning |
|
* |
|
* recommended view: Full Page |
|
* tested on Firefox v96.0.1/x64, Chrome v97.0.4692.99/x64, Brave v1.31.87/x64, Edge v97.0.1072.76, and Chrome for Android v81.0.4044.138/Android 4.4.2 |
|
* |
|
* more info. in the details view |
|
*/ |
|
|
|
@use 'sass:math'; |
|
@use 'sass:color'; |
|
|
|
// === petal geometry === |
|
$large-petals-count: 12; |
|
$small-petals-count: 8; |
|
|
|
// === colors for petal generation === |
|
$large-petal-color: #ffcc3b; |
|
$mid-petal-color-1: #ffb534; |
|
$mid-petal-color-2: #ff8f2c; |
|
$small-petal-color: #ffd234; |
|
$petal-root-color: #d06500; |
|
|
|
// === as stated in the official documentation (https://sass-lang.com/documentation/breaking-changes/slash-div), using slash (/) as a division operation was deprecated since `Dart Sass v1.33.0` and developers are encouraged to use `math.div` function instead. At present, CodePen offers `Dart Sass 1.32.0`. Hence, I've made a tiny wrapper function for backwards compatibility === |
|
@function devide($n, $dn) { |
|
// @return math.div($n, $dn); |
|
@return $n / $dn; |
|
} |
|
|
|
html { |
|
--zoom: 120; |
|
|
|
// === animation configuration === |
|
--animation-speed: 100; // any value between 1-200. a low value will make the animation slower. values more than 200 will make it look static |
|
|
|
--animation-delay: 500ms; // the compiled CSS is over 40KB and requires more computation effort, making the animation glitchy. we add a delay to ensure browsers can process the CSS before the animation begins |
|
|
|
--leaf-duration: 1s; |
|
--leafs-start-from: 100deg; |
|
--leaf-1-delay: 500ms; |
|
--leaf-2-delay: 580ms; |
|
--leaf-3-delay: 600ms; |
|
--leaf-timing-func: cubic-bezier(0.18, 0.89, 0.32, 1.28); |
|
|
|
--petal-duration: 1s; |
|
--petals-start-from: 50deg; |
|
--petal-1-delay: 0ms; |
|
--petal-2-delay: 100ms; |
|
--petal-3-delay: 200ms; |
|
--petal-timing-func: cubic-bezier(0.68, -0.55, 0.27, 1.55); |
|
|
|
// === appearance === |
|
--color-leaf: hsl(175, 30%, 31%); |
|
--color-leaf-root: hsl(113, 21%, 51%); |
|
--scene-drop-shadow: drop-shadow(11em 17em 27em rgba(0, 0, 0, 0.37)); |
|
|
|
--petals-offset: 36em; |
|
--petals-large-radius: 204em; |
|
--petals-mid-radius: 136em; |
|
--petals-small-radius: 82em; |
|
|
|
filter: var(--scene-drop-shadow); |
|
} |
|
|
|
// === converts hex colors to rgb for use in the generated SVG === |
|
@function rgb-color($color) { |
|
$r: color.red($color); |
|
$g: color.green($color); |
|
$b: color.blue($color); |
|
|
|
@return "rgb(#{$r},#{$g},#{$b})"; |
|
} |
|
|
|
:root { |
|
--unit: 1vmin; |
|
--available-screen-min: 665; |
|
--px: calc(var(--zoom) * (var(--unit) / var(--available-screen-min))); |
|
} |
|
|
|
// === quick and dirty reset. also setting the font-size enables us to use em as a responsive unit === |
|
html, |
|
body, |
|
head { |
|
height: 100%; |
|
position: relative; |
|
padding: 0; |
|
margin: 0; |
|
font-size: calc(1 * var(--px)); |
|
background-color: transparent; |
|
} |
|
|
|
// === we also need to display <head> as a block element so that we can use its pseudo-elements === |
|
head { |
|
width: 100%; |
|
position: absolute; |
|
left: 0; |
|
top: 0; |
|
display: block; |
|
} |
|
|
|
head::before, |
|
head::after, |
|
body::before, |
|
body::after, |
|
html::before, |
|
html::after { |
|
content: ""; |
|
position: absolute; |
|
background-repeat: no-repeat; |
|
|
|
// === the following effectively calls the GPU and makes the animation noticeably smoother in Chrome === |
|
filter: blur(0); |
|
} |
|
|
|
// === this returns a polygon clip-path for any possible n-gon (where n = 3, ..., 100) === |
|
// === percentages are encoded as %25 for use in encoded SVG === |
|
@function generate-polygon($n) { |
|
$theta: devide(2 * math.$pi, $n); |
|
$point-array: null; |
|
|
|
@for $i from 1 through $n { |
|
$a: $theta * $i; |
|
$x: 50% + 50% * math.sin($a); |
|
$y: 50% + 50% * math.cos($a); |
|
$point-array: $point-array, #{$x}25 #{$y}25; |
|
} |
|
|
|
@return polygon($point-array); |
|
} |
|
|
|
// === this generates n number of divs representing their index in --i CSS variable === |
|
// === i would like to call them "indexed div"s or idivs === |
|
@function generate-idivs($n) { |
|
$str: null; |
|
@for $i from 0 to $n { |
|
$str: $str + "%3Cdiv style='--i:#{$i}'%3E%3C/div%3E"; |
|
} |
|
@return $str; |
|
} |
|
|
|
// === this returns a line tilted to the desired angle === |
|
@function leaf-root($angle: 130deg) { |
|
@return linear-gradient( |
|
$angle, |
|
#fff0 0, |
|
#fff0 calc(50% - 3em), |
|
var(--color-leaf-root) 50%, |
|
#fff0 calc(50% + 1em), |
|
#fff0 100% |
|
); |
|
} |
|
|
|
// === this function places a handful of squares around a circular path. those squares are embedded DOM elements within an SVG container. it returns a background image === |
|
@function place-petals( |
|
$n, |
|
$size-var, |
|
$ratio-var, |
|
$fill, |
|
$rotate-start: 45deg, |
|
$start: 0.5 |
|
) { |
|
$theta: devide(2 * math.$pi, $n); |
|
$shift: $theta * $start; |
|
$layers: null; |
|
|
|
@for $i from 1 through $n { |
|
$angle: ($i * $theta) + $shift; |
|
$x: calc(50% + var(#{$ratio-var}) * #{math.cos($angle)}); |
|
$y: calc(50% + var(#{$ratio-var}) * #{math.sin($angle)}); |
|
|
|
$layer-image: url("data:image/svg+xml,\ |
|
%3Csvg viewBox='0 0 100 100' width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E\ |
|
%3CforeignObject width='100%25' height='100%25'%3E\ |
|
%3Cdiv xmlns='http://www.w3.org/1999/xhtml'%3E%3Cdiv class='A'%3E%3C/div%3E%3C/div%3E\ |
|
%3Cstyle%3E\ |
|
.A{\ |
|
position:absolute;left:50px;top:50px;\ |
|
width:70px;height:70px;border-radius:8%25;\ |
|
transform:translate(-50%25,-50%25) rotate(#{$rotate-start}) rotate(#{$angle}rad);\ |
|
background:#{rgb-color($fill)};\ |
|
}\ |
|
%3C/style%3E%3C/foreignObject%3E%3C/svg%3E"); |
|
|
|
$this-layer: $layer-image |
|
no-repeat |
|
scroll |
|
$x |
|
$y / |
|
var(#{$size-var}) |
|
var(#{$size-var}); |
|
$layers: $layers, $this-layer; |
|
} |
|
|
|
@return $layers; |
|
} |
|
|
|
// === this function creates a camera shutter-like shape by creating a polygon and extending their edges === |
|
@function create-shutter($n, $size: 25, $thickness: 1, $color: black, $area) { |
|
$grid: 1000; |
|
$multiplier: $grid * 0.01; |
|
|
|
$shift: 0.25; |
|
@if $n % 2 == 0 { |
|
$shift: 0.5; |
|
} |
|
|
|
$rotate: devide(360, $n) * $shift; |
|
|
|
$match: $rotate * -1; |
|
@if $n % 4 == 0 { |
|
$match: 0; |
|
} |
|
|
|
@if $n == 3 { |
|
$match: 90; |
|
} |
|
|
|
$wrapper: url("data:image/svg+xml,\ |
|
%3Csvg viewBox='0 0 #{$grid} #{$grid}' \ |
|
width='#{$grid}' height='#{$grid}' \ |
|
xmlns='http://www.w3.org/2000/svg'%3E\ |
|
%3CforeignObject width='100%' height='100%'%3E\ |
|
%3Cdiv xmlns='http://www.w3.org/1999/xhtml'%3E\ |
|
%3Cdiv class='c' style='--n:#{$n}'%3E#{generate-idivs($n)}%3C/div%3E\ |
|
%3C/div%3E\ |
|
%3Cstyle%3E\ |
|
.c{\ |
|
--r:#{$size * $multiplier}px;\ |
|
--j:calc(360 / var(--n));\ |
|
--b:calc((var(--j) * 0.5 + 90) * 1deg);\ |
|
--s:#{$rotate}deg;\ |
|
--c:translate(-50%25,-50%25);\ |
|
transform:var(--c) rotate(var(--s));\ |
|
width:100%25;height:100%25;\ |
|
border-radius:50%25;\ |
|
overflow:hidden;\ |
|
}\ |
|
.c, .c::before, .c div{\ |
|
position:absolute;left:50%25;top:50%25;\ |
|
}\ |
|
.c::before{\ |
|
content:'';padding:var(--r);transform:var(--c) rotate(#{$match}deg);\ |
|
clip-path:#{generate-polygon($n)};\ |
|
}\ |
|
.c div{\ |
|
--a:calc(var(--j) * var(--i) * 1deg);\ |
|
width:#{$grid}px;height:#{$thickness * $multiplier}px;\ |
|
transform:var(--c) rotate(var(--a)) translate(calc(var(--r) * -1)) rotate(var(--b)) var(--c);\ |
|
}\ |
|
.c::before, .c div{\ |
|
background:#{rgb-color($color)};\ |
|
}\ |
|
%3C/style%3E\ |
|
%3C/foreignObject%3E\ |
|
%3C/svg%3E"); |
|
|
|
@return $wrapper no-repeat scroll center center / #{$area} #{$area}; |
|
} |
|
|
|
// ============= |
|
// === LEAFS === |
|
// ============= |
|
head::before, |
|
head::after, |
|
html::before { |
|
transform-origin: right top; // === we want to animate their position from the top-right corner === |
|
background-size: 100% 100%; |
|
background-color: var(--color-leaf); |
|
box-shadow: inset 0 0 12em 10em var(--color-leaf); |
|
} |
|
|
|
head::before { |
|
left: calc(50% - 65em); |
|
top: calc(50% + 109em); |
|
transform: translate(-50%, -50%) scale(0) rotate(var(--leafs-start-from)) |
|
rotate(80deg) skewX(6deg) skewY(-30deg); |
|
width: 83em; |
|
height: 128em; |
|
|
|
border-radius: 50% 9% 32% 4%; |
|
background-image: leaf-root(2deg), leaf-root(2deg), leaf-root(2deg), |
|
leaf-root(92deg), leaf-root(92deg), leaf-root(92deg), leaf-root(); |
|
background-position: -56em 30em, -34em 3em, -14em -22em, -16em 96em, 6em 68em, |
|
27em 43em, 0 15em; |
|
} |
|
|
|
head::after { |
|
left: calc(50% - 94em); |
|
top: calc(50% + 98em); |
|
transform: translate(-50%, -50%) scale(0) rotate(var(--leafs-start-from)) |
|
rotate(-44deg) skewY(-30deg); |
|
width: 83em; |
|
height: 96em; |
|
|
|
border-radius: 25% 9% 32% 4%; |
|
background-image: leaf-root(-93deg), leaf-root(-93deg), leaf-root(-93deg), |
|
leaf-root(-1deg), leaf-root(-1deg), leaf-root(-1deg), leaf-root(); |
|
background-position: -12em 64em, 7em 41em, 28em 16em, -56em 16em, -36em -8em, |
|
-15em -32em, 0 0; |
|
} |
|
|
|
html::before { |
|
left: calc(50% - 120em); |
|
top: calc(50% + 123em); |
|
transform: translate(-50%, -50%) scale(0) rotate(var(--leafs-start-from)) |
|
skewX(-15deg) skewY(-18deg); |
|
width: 113em; |
|
height: 121em; |
|
|
|
border-radius: 24% 9% 27% 7%; |
|
background-image: leaf-root(-90deg), leaf-root(-90deg), leaf-root(-90deg), |
|
leaf-root(2deg), leaf-root(1deg), leaf-root(0deg), leaf-root(133deg); |
|
background-position: -19em 79em, 9em 50.4em, 35em 21.4em, -21em -41.6em, |
|
-49em -11em, -74em 18em, 0 0; |
|
} |
|
|
|
// ============= |
|
// === PETALS === |
|
// ============= |
|
body::before, |
|
body::after, |
|
html::after { |
|
transform: translate(-50%, -50%) rotate(var(--petals-start-from)) scale(0); |
|
left: calc(50% + var(--petals-offset)); |
|
top: calc(50% - var(--petals-offset)); |
|
} |
|
|
|
body::before { |
|
padding: var(--petals-large-radius); |
|
|
|
--pointer-size: 73em; |
|
--pointer-ratio: 46%; |
|
--hand-size: 104em; |
|
--hand-ratio: 39.3%; |
|
--fill-size: 220em; |
|
--shine-size: 325em; |
|
--shine-color: rgba(255, 255, 255, 0.29); |
|
--fill: linear-gradient(#{$large-petal-color} 0 0) no-repeat scroll 50% 50% / |
|
var(--fill-size) var(--fill-size); |
|
--shine: radial-gradient( |
|
circle at center, |
|
var(--shine-color) 50%, |
|
transparent 72% |
|
) |
|
no-repeat 50% 50% / var(--shine-size) var(--shine-size); |
|
|
|
filter: drop-shadow(-25em 10em 7em rgba(254, 136, 10, 0.1)); |
|
background: var(--shine), var(--fill), |
|
place-petals( |
|
$large-petals-count, |
|
"--hand-size", |
|
"--hand-ratio", |
|
$large-petal-color, |
|
0, |
|
0.5 |
|
), |
|
place-petals( |
|
$large-petals-count, |
|
"--pointer-size", |
|
"--pointer-ratio", |
|
$large-petal-color |
|
); |
|
} |
|
|
|
body::after { |
|
padding: var(--petals-mid-radius); |
|
|
|
--pointer-size: 56em; |
|
--pointer-ratio: 49%; |
|
--hand-size: 77em; |
|
--hand-ratio: 40.3%; |
|
--fill-size: 107em; |
|
--fill: linear-gradient(#{$mid-petal-color-2} 0 0) no-repeat 50% 50% / |
|
var(--fill-size) var(--fill-size); |
|
|
|
background: var(--fill), |
|
place-petals( |
|
$large-petals-count, |
|
"--pointer-size", |
|
"--pointer-ratio", |
|
$mid-petal-color-1, |
|
45deg, |
|
0 |
|
), |
|
place-petals( |
|
$large-petals-count, |
|
"--hand-size", |
|
"--hand-ratio", |
|
$mid-petal-color-2, |
|
0, |
|
0 |
|
); |
|
} |
|
|
|
html::after { |
|
padding: var(--petals-small-radius); |
|
|
|
--pointer-size: 39em; |
|
--pointer-ratio: 48.2%; |
|
--hand-size: 54em; |
|
--hand-ratio: 39.4%; |
|
--fill-size: 49em; |
|
--shine-size: 130em; |
|
--circle-1-size: 100em; |
|
--shutter-size: 70em; |
|
--fill: linear-gradient(#{$mid-petal-color-2} 0 0) no-repeat 50% 50% / |
|
var(--fill-size) var(--fill-size); |
|
--shine: radial-gradient( |
|
circle at center, |
|
#{$mid-petal-color-2} 42%, |
|
transparent 72% |
|
) |
|
no-repeat 50% 50% / var(--shine-size) var(--shine-size); |
|
--circle-1: radial-gradient( |
|
circle at center, |
|
#{$small-petal-color} 48%, |
|
transparent 50% |
|
) |
|
no-repeat 50% 50% / var(--circle-1-size) var(--circle-1-size); |
|
|
|
background: create-shutter(10, 20, 1, $petal-root-color, var(--shutter-size)), |
|
var(--circle-1), var(--shine), var(--fill), |
|
place-petals( |
|
$small-petals-count, |
|
"--hand-size", |
|
"--hand-ratio", |
|
$small-petal-color, |
|
0, |
|
0 |
|
), |
|
place-petals( |
|
$small-petals-count, |
|
"--pointer-size", |
|
"--pointer-ratio", |
|
$small-petal-color, |
|
45deg, |
|
0 |
|
); |
|
} |
|
|
|
// === keyframes and animation === |
|
html { |
|
--ams: calc(100 / var(--animation-speed)); |
|
} |
|
|
|
body::before, |
|
body::after, |
|
html::after { |
|
animation: bloom var(--petal-timing-func) |
|
calc(var(--petal-duration) * var(--ams)) forwards; |
|
} |
|
|
|
body::before { |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--petal-1-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
body::after { |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--petal-2-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
html::after { |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--petal-3-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
@keyframes bloom { |
|
to { |
|
transform: translate(-50%, -50%) rotate(0deg) scale(1); |
|
} |
|
} |
|
|
|
head::before, |
|
head::after, |
|
html::before { |
|
animation: var(--leaf-timing-func) calc(var(--leaf-duration) * var(--ams)) |
|
forwards; |
|
} |
|
|
|
head::before { |
|
animation-name: leaf1; |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--leaf-1-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
head::after { |
|
animation-name: leaf2; |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--leaf-2-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
html::before { |
|
animation-name: leaf3; |
|
animation-delay: calc( |
|
(var(--animation-delay) + var(--leaf-3-delay)) * var(--ams) |
|
); |
|
} |
|
|
|
@keyframes leaf1 { |
|
to { |
|
transform: translate(-50%, -50%) scale(1) rotate(0deg) rotate(80deg) |
|
skewX(6deg) skewY(-30deg); |
|
} |
|
} |
|
|
|
@keyframes leaf2 { |
|
to { |
|
transform: translate(-50%, -50%) scale(1) rotate(0deg) rotate(-44deg) |
|
skewY(-30deg); |
|
} |
|
} |
|
|
|
@keyframes leaf3 { |
|
to { |
|
transform: translate(-50%, -50%) scale(1) rotate(0deg) skewX(-15deg) |
|
skewY(-18deg); |
|
} |
|
} |