- const MODES = ['normal', 'screen', 'multiply', 'overlay', 'hard-light', 'soft-light', 'color-dodge', 'color-burn'];
//- limits within which the alpha may vary (in %)
- const ALPHA_MIN = 20, ALPHA_MAX = 100;
- let alpha_val = .5*(ALPHA_MIN + ALPHA_MAX), m = 5; 

//- pass alpha and blend mode to CSS as custom properties
body(style=`--alpha: ${alpha_val}; --mode: ${MODES[m]}`)
	// contains nothing but filter => functionally same as a style elem
	// zero its dimensions and hide it from screen readers
	svg(width='0' height='0' aria-hidden='true')
		// restrict filter region to input box
		// sRGB is what we normally want (but not default)
		filter#noise(x='0' y='0' width='1' height='1'
								 color-interpolation-filters='sRGB')
			// generate fine noise
			feTurbulence(type='fractalNoise' baseFrequency='.713' numOctaves='4')
			// fully desaturate noise
			feColorMatrix(type='saturate' values='0')
	
	// control alpha of noise layer and blend mode
	form.controls
		// instead of fieldset for better styling options
		div.alpha(role='group' aria-label='Control panel for setting the noise layer alpha.' style=`--min: ${ALPHA_MIN}; --max: ${ALPHA_MAX}`)
			label(for='alpha') Noise alpha
			// div wrapper for layout purposes :(
			div.range
				input#alpha(type='range' min=ALPHA_MIN max=ALPHA_MAX value=alpha_val)
				output(for='alpha')
		div.blend(role='group' aria-label='Control panel for setting the blend mode used to blend the noise layer with the original gradient.')
			label(for='mode') Blend mode
			select#mode
				button: selectedcontent
				- MODES.forEach((c, i) => {
					option(value=c label=c selected=m === i) #{c}
				- })
	
	// visual result section with 3 panels
	section.results
		.card.bands: span original gradient (banding)
		.card.noise: span noise over white/ black
		.card.layer: span blend noise with gradient
View Compiled
// for slider styling
$track-h: 6px;
$track-r: .5*$track-h;
$thumb-d: 1.25em;
$thumb-r: .5*$thumb-d;

// why mixins? see 
// https://css-tricks.com/sliding-nightmare-understanding-range-input/
@mixin track() {
	height: $track-h;
	border-radius: $track-r;
	background: 
		linear-gradient(90deg, 
				var(--hlc) var(--pos), 
				currentcolor 0)
}

@mixin thumb($f: 0) {
	/* Firefox only */
	@if $f == 0 { border: none }
	/* WebKit only */
	@else { margin-top: calc(#{$track-r} + -1*#{$thumb-r}) }
	
	// can't use aspect-ratio here
	width: $thumb-d;
	height: $thumb-d;
	border-radius: 50%;
	background: var(--hlc);
	cursor: ew-resize
}

/* set for controls */
* { font: inherit }

html, body, div {
	display: grid;
	grid-gap: .5em
}

/* take it out of document flow */
svg[height='0'][aria-hidden='true'] { position: fixed }

html {
	/* stretch across at least 1 viewport */
	min-height: 100%;
	/* my attempt at a pretty light and dark palette
	 * of course it didn't come out pretty, 
	 * what in Odin's name was I thinking? */
	color-scheme: light dark;
	background: light-dark(#e88c7d, #467ec3);
	color: light-dark(#283c55, #eebba0)
}

body {
	/* controls take up as much vertical space as needed, 
	 * allow result to fill out the remaining vertical space */
	/* restrict it all horizontally */
	grid-template: 1fr auto/ min(100%, 75em);
	/* middle align as necessary */
	place-content: center;
	/* vary font size, but within certain limits */
	font: clamp(1em, 2vw, 1.5em)/ 1.125 cairo, sans-serif
}

/* flexbox everything! */
form, section, [role='group'] {
	display: flex;
	flex-wrap: wrap;
	gap: .5em
}

form, span { background: light-dark(#eebba0, #022450) }

form {
	--hlc: light-dark(#71093b, #eaaa34);
	--fgc: light-dark(#eaaa34, #71093b);
	/* I don't even remember why I added this 
	 * or why removing it breaks things ¯\_(ツ)_/¯ */
	container-type: inline-size;
	justify-content: center;
	/* separate alpha & blend mode controls 
	 * with a bit more space */
	gap: 1em;
	z-index: 1; /* Firefox */
	padding: .5em
}

[role='group'] {
	/* middle align everything, looks ugly otherwise */
	align-items: center;
	justify-content: center
}

/* visual interaction hint */
input, select { cursor: pointer }

/* what I Satan's name am I doing? no idea ¯\_(ツ)_/¯ */
/* https://css-tricks.com/four-layouts-for-the-price-of-one/ */
.alpha, .range { flex: 1 1 30ch }

/* compute a few basic things */
.alpha {
	--dif: (var(--alpha) - var(--min));
	--rng: (var(--max) - var(--min));
	--prg: var(--dif)/var(--rng)
}

/* to be able to use cqw for its children */
.range { container-type: inline-size }

output, select {
	font-family: roboto mono, monospace
}

[type='range'] {
	/* allow styling in WebKit browsers */
	&, &::-webkit-slider-thumb { appearance: none }
	
	/* default sucks huge donkey dicks */
	background: #0000;
	
	&::-webkit-slider-runnable-track { @include track }
	&::-moz-range-track { @include track }
	/* Firefox supports this, yo! */
	&::slider-track { @include track }
	
	/* different styles for different browser engines
	 * thanks to mixin params */
	&::-webkit-slider-thumb { @include thumb(1) }
	&::-moz-range-thumb { @include thumb }
	/* Firefox supports this, yo! */
	&::slider-thumb { @include thumb }
	
	&, & + output {
		/* middle of thumb position */
		--pos: calc(#{$thumb-r} + var(--prg)*(100cqw - #{$thumb-d}));
		/* stack them up
		 * https://mastodon.social/@anatudor/113774781963966085 */
		grid-area: 1/ 1
	}
	
	& + output {
		/* left align */
		justify-self: start;
		/* give text inside room to breathe */
		padding: .5em;
		/* middle align with thumb, move above slider */
		translate: calc(var(--pos) - 50%) -100%;
		background: var(--hlc);
		color: var(--fgc);
		/* hack to display numeric var in content 
		 * (which requites string) */
		counter-reset: a var(--alpha);
		
		&::after { content: counter(a) '%' }
	}
}

.blend {
	flex: 0 0 min-content;
	min-width: calc(13ch + 2em);
	
	/* oh, wait, this is why removing parent containr broke things */
	/* also boolean logic! 
	 * https://mastodon.social/@anatudor/114606670001499403 */
	@container ((width > 200px) and (width < 388px)) or (width > 580px) {
		flex-basis: max-content
	}
}

/* custom style select, hello!
 * https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Customizable_select */
select,
::picker(select) {
  appearance: base-select;
	border: solid 1px #0000;
	min-width: 13ch;
	border-radius: 0;
	background: light-dark(#eaac8b, #355070) padding-box
}

::picker(select) {
	overflow: hidden;
	height: max-height
}

option {
	white-space: nowrap;
	
	&:is(:hover, :focus) {
		background: light-dark(#990b52, #cb8b15);
		color: var(--fgc);
	}
	
	&:checked {
		background: var(--hlc);
		font-weight: bold
	}
}

/* visual result */
section {
	/* wide screen case, along x axis */
	--dir: 0;
	grid-row: 1;
	
	/* narrow screen case, along y axis */
	@container (max-width: 720px) {
		--dir: 1;
		flex-direction: column
	}
}

.card {
	/* white/ black split angle */
	--ang: calc((2 - var(--dir))*90deg);
	flex: 1 1 30%;
	min-height: calc((3 - 2*var(--dir))*8em);
	/* cut out any filter edge weirdness */
	clip-path: inset(1px);
	
	/* no blending for first two panels */
	&:nth-child(-n + 2) { --mode: normal }
	/* noise layer fully transparent for first panel */
	&:nth-child(1) { --alpha: 0 }
	/* white black split instead of example gradient for panel two */
	&:nth-child(2) { --slist: #fff 50%, #000 0 }
	
	&::before, &::after {
		grid-area: 1/ 1;
		content: ''
	}
	
	/* panel background gradient */
	&::before {
		background: 
			linear-gradient(var(--ang), var(--slist, #a9613a, #1e1816));
	}
	
	/* noise layer */
	&::after {
		opacity: calc(.01*var(--alpha));
		filter: url(#noise);
		mix-blend-mode: var(--mode)
	}
	
	/* panel label */
	span {
		place-self: start;
		grid-area: 1/ 1;
		z-index: 2;
		margin: .5em;
		padding: .5em;
		font-family: roboto condensed, sans-serif
	}
}
View Compiled
/* all the JS does is update one custom property 
 * when changing the slider or select value */
addEventListener('input', e => {
	// get control that was modified
	let _t = e.target;
	// update corresponding custom property on body
	document.body.style.setProperty(`--${_t.id}`, _t.value)
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.