2025-05-29 17:03:50 -05:00
/* REGULAR SCANLINES SETTINGS */
2025-06-24 22:38:25 -04:00
// width of 1 scanline (responsive units to prevent banding)
2025-05-29 17:03:50 -05:00
$scan-width : 1 px ;
2025-06-24 22:38:25 -04:00
$scan-width-scaled : 0 .15 vh ; // viewport-relative unit for better scaling
2025-05-29 17:03:50 -05:00
// emulates a damage-your-eyes bad pre-2000 CRT screen ♥ (true, false)
$scan-crt : false ;
// frames-per-second (should be > 1), only applies if $scan-crt: true;
$scan-fps : 20 ;
// scanline-color (rgba)
$scan-color : rgba ( #000 , .3 ) ;
// set z-index on 8, like in ♥ 8-bits ♥, or…
// set z-index on 2147483648 or more to enable scanlines on Chrome fullscreen (doesn't work in Firefox or IE);
$scan-z-index : 2147483648 ;
/* MOVING SCANLINE SETTINGS */
// moving scanline (true, false)
$scan-moving-line : true ;
// opacity of the moving scanline
$scan-opacity : .75 ;
/* MIXINS */
// apply CRT animation: @include scan-crt($scan-crt);
@mixin scan-crt ( $scan-crt ) {
@if $scan-crt == true {
animation : scanlines 1 s steps ( $scan-fps ) infinite ;
}
@else {
animation : none ;
}
}
// apply CRT animation: @include scan-crt($scan-crt);
@mixin scan-moving ( $scan-moving-line ) {
@if $scan-moving-line == true {
animation : scanline 6 s linear infinite ;
}
@else {
animation : none ;
}
}
/* CSS .scanlines CLASS */
. scanlines {
position : relative ;
overflow : hidden ; // only to animate the unique scanline
& : before ,
& : after {
display : block ;
pointer-events : none ;
content : ' ' ;
position : absolute ;
}
// unique scanline travelling on the screen
& : before {
// position: absolute;
// bottom: 100%;
width : 100 % ;
height : $scan-width * 1 ;
z-index : $scan-z-index + 1 ;
background : $scan-color ;
opacity : $scan-opacity ;
// animation: scanline 6s linear infinite;
@include scan-moving ( $scan-moving-line ) ;
}
2025-06-24 22:38:25 -04:00
// the scanlines, so! - with responsive scaling for low-res displays
2025-05-29 17:03:50 -05:00
& : after {
top : 0 ;
right : 0 ;
bottom : 0 ;
left : 0 ;
z-index : $scan-z-index ;
2025-06-28 13:05:04 -04:00
// repeating-linear-gradient is more efficient than linear-gradient+background-size because it doesn't require the browser to calculate tiling
background : repeating-linear-gradient ( to bottom ,
transparent 0 ,
transparent $ scan-width ,
$ scan-color $ scan-width ,
$ scan-color calc ( $ scan-width * 2 ) ) ;
2025-05-29 17:03:50 -05:00
@include scan-crt ( $scan-crt ) ;
2025-06-24 22:38:25 -04:00
// Prevent sub-pixel aliasing on scaled displays
image-rendering : crisp-edges ;
image-rendering : pixelated ;
}
2025-06-28 13:05:04 -04:00
// Scanlines use dynamic thickness calculated by JavaScript
// JavaScript calculates optimal thickness to prevent banding at any scale factor
// The --scanline-thickness custom property is set by applyScanlineScaling()
// The modes (hairline, thin, medium, thick) force the base thickness selection
// Some modes may appear the same (e.g. hairline and thin) depending on the display
& : before {
height : var ( -- scanline-thickness , $scan-width ) ;
2025-06-24 22:38:25 -04:00
}
2025-06-28 13:05:04 -04:00
& : after {
background : repeating-linear-gradient ( to bottom ,
transparent 0 ,
transparent var ( - - scanline-thickness , $ scan-width ) ,
$ scan-color var ( - - scanline-thickness , $ scan-width ) ,
$ scan-color calc ( var ( - - scanline-thickness , $ scan-width ) * 2 ) ) ;
2025-05-29 17:03:50 -05:00
}
}
/* ANIMATE UNIQUE SCANLINE */
@keyframes scanline {
0 % {
transform : translate3d ( 0 , 200000 % , 0 ) ;
// bottom: 0%; // to have a continuous scanline move, use this line (here in 0% step) instead of transform and write, in &:before, { position: absolute; bottom: 100%; }
}
}
@keyframes scanlines {
0 % {
background-position : 0 50 % ;
// bottom: 0%; // to have a continuous scanline move, use this line (here in 0% step) instead of transform and write, in &:before, { position: absolute; bottom: 100%; }
}
2025-06-24 22:38:25 -04:00
}