<!--TS57 - Модификация для Тильды. Курсор-маска https://mod.tistols.com/hover-cursor -->
<!--
bottom: Ссылка на нижнее фото или видео. (Видно по умолчанию; тег img или video скрипт подставит сам по расширению файла)
top: Ссылка на верхнее фото или видео. (Проявляется в круге под курсором; тег определяется автоматически)
height: Высота блока. (Любое CSS-значение: 100vh — на весь экран, или, например, 600px)
size: Диаметр зоны проявления в пикселях. (Размер круга, который следует за курсором)
solid: Радиус чёткой части в процентах. (До этого значения верхнее медиа видно на 100%)
fade: Радиус затухания в процентах. (Где проявление полностью исчезает; разница между fade и solid даёт мягкость края)
ease: Плавность следования за курсором. (От 0.01 до 1: меньше — мягче и «ленивее», 1 — мгновенно)
background: Цвет фона под слоями. (Виден до загрузки медиа и по краям кадра)
Можно вставить блок несколько раз на странице — каждый работает со своими настройками.
Если оставить bottom или top пустыми — покажутся встроенные картинки-заглушки (удобно для проверки).
-->
<div
class="gc-reveal"
data-bottom="https://static.tildacdn.com/tild3632-3430-4863-b365-653233303966/432453.jpg"
data-top="https://static.tildacdn.com/tild6462-3137-4239-b836-396265626463/646345.jpg"
data-height="100vh"
data-size="600"
data-solid="36"
data-fade="90"
data-ease="0.16"
data-background="#1a0a0f"
></div>
<style>
.gc-reveal{
position:relative;
width:100%;
height:var(--gc-height,100vh);
overflow:hidden;
background:var(--gc-bg,#1a0a0f);
}
.gc-reveal__layer{ position:absolute; inset:0; pointer-events:none; }
.gc-reveal__layer img,
.gc-reveal__layer video{
width:100%; height:100%; object-fit:cover; display:block;
-webkit-user-select:none; user-select:none;
}
.gc-reveal__bottom{ z-index:1; }
/* круглая маска, которая едет за курсором.
closest-side — чтобы затухание заканчивалось до краёв плитки (иначе виден квадрат) */
.gc-reveal__top{
z-index:2;
-webkit-mask-image:var(--gc-mask, radial-gradient(circle closest-side at center,#000 0%,#000 46%,rgba(0,0,0,0) 90%));
mask-image:var(--gc-mask, radial-gradient(circle closest-side at center,#000 0%,#000 46%,rgba(0,0,0,0) 90%));
-webkit-mask-size:var(--gc-size,400px) var(--gc-size,400px);
mask-size:var(--gc-size,400px) var(--gc-size,400px);
-webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
-webkit-mask-position:var(--gc-x,-2000px) var(--gc-y,-2000px);
mask-position:var(--gc-x,-2000px) var(--gc-y,-2000px);
}
</style>
<script>
(function(){
// нижнее/верхнее фото-заглушки (если ссылка не задана)
function placeholder(label, c1, c2, tc){
return "data:image/svg+xml;utf8,"
+ "<svg xmlns='http://www.w3.org/2000/svg' width='1200' height='800' preserveAspectRatio='xMidYMid slice'>"
+ "<defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>"
+ "<stop offset='0' stop-color='"+c1+"'/><stop offset='1' stop-color='"+c2+"'/></linearGradient></defs>"
+ "<rect width='1200' height='800' fill='url(%23g)'/>"
+ "<text x='600' y='430' font-family='Arial,sans-serif' font-size='120' font-weight='bold' fill='"+tc+"' text-anchor='middle'>"+label+"</text></svg>";
}
// создаёт <img> или <video> по ссылке (тип — по расширению файла)
function makeMedia(url){
url = (url || '').trim();
var isVideo = /\.(mp4|webm|ogg|ogv|mov|m4v)(\?|#|$)/i.test(url);
if(isVideo){
var v = document.createElement('video');
v.src = url;
v.autoplay = true; v.loop = true; v.muted = true; v.defaultMuted = true; v.playsInline = true;
v.setAttribute('autoplay',''); v.setAttribute('loop','');
v.setAttribute('muted',''); v.setAttribute('playsinline','');
return v;
}
var img = document.createElement('img');
img.src = url; img.alt = '';
return img;
}
function init(root){
root.setAttribute('data-gc-init','');
var bottomURL = root.getAttribute('data-bottom') || placeholder('BASE IMAGE','rgb(45,15,26)','rgb(15,7,11)','rgb(120,50,70)');
var topURL = root.getAttribute('data-top') || placeholder('REVEAL','rgb(255,148,114)','rgb(212,71,106)','rgb(255,255,255)');
var height = root.getAttribute('data-height') || '100vh';
var size = parseFloat(root.getAttribute('data-size')) || 400;
var solid = root.getAttribute('data-solid') || '46';
var fade = root.getAttribute('data-fade') || '90';
var ease = parseFloat(root.getAttribute('data-ease')) || 0.16;
var bg = root.getAttribute('data-background') || '#1a0a0f';
// собираем слои
var bottom = document.createElement('div');
bottom.className = 'gc-reveal__layer gc-reveal__bottom';
bottom.appendChild(makeMedia(bottomURL));
var top = document.createElement('div');
top.className = 'gc-reveal__layer gc-reveal__top';
top.appendChild(makeMedia(topURL));
root.appendChild(bottom);
root.appendChild(top);
// применяем настройки через CSS-переменные
root.style.setProperty('--gc-height', height);
root.style.setProperty('--gc-bg', bg);
root.style.setProperty('--gc-size', size + 'px');
root.style.setProperty('--gc-mask',
'radial-gradient(circle closest-side at center, #000 0%, #000 ' + solid + '%, rgba(0,0,0,0) ' + fade + '%)');
// взаимодействие с курсором
var mouse = {x:0,y:0}, cur = {x:0,y:0}, hovering = false;
function setPos(p){
var r = root.getBoundingClientRect();
mouse.x = p.clientX - r.left;
mouse.y = p.clientY - r.top;
}
root.addEventListener('mouseenter', function(){ hovering = true; });
root.addEventListener('mouseleave', function(){ hovering = false; });
root.addEventListener('mousemove', setPos);
root.addEventListener('touchstart', function(e){ hovering=true; if(e.touches.length) setPos(e.touches[0]); }, {passive:true});
root.addEventListener('touchmove', function(e){ if(e.touches.length) setPos(e.touches[0]); }, {passive:true});
root.addEventListener('touchend', function(){ hovering = false; });
(function loop(){
cur.x += (mouse.x - cur.x) * ease;
cur.y += (mouse.y - cur.y) * ease;
if(hovering){
root.style.setProperty('--gc-x', (cur.x - size/2) + 'px');
root.style.setProperty('--gc-y', (cur.y - size/2) + 'px');
} else {
root.style.setProperty('--gc-x', '-2000px');
root.style.setProperty('--gc-y', '-2000px');
}
requestAnimationFrame(loop);
})();
}
function boot(){
var nodes = document.querySelectorAll('.gc-reveal:not([data-gc-init])');
for(var i=0;i<nodes.length;i++) init(nodes[i]);
}
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();
</script>