/* ---------- случайная картина из Wikimedia Commons (без ключей) ---------- */ (function(){ var frame=document.getElementById('art-frame'),cap=document.getElementById('art-cap'),srcEl=document.getElementById('art-src'),btn=document.getElementById('art-next'); if(!frame||!cap||!btn)return; function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]})} function rint(n){return Math.floor(Math.random()*n)} function pick(a){return a[rint(a.length)]} function getJSON(u){return fetch(u).then(function(r){if(!r.ok)throw new Error(r.status+' '+u);return r.json()})} /* единственный источник: Wikimedia Commons (ключ не нужен, origin=* включает CORS) */ function plain(h){return new DOMParser().parseFromString(String(h||''),'text/html').body.textContent.replace(/\s+/g,' ').trim()} /* хвост выгрузки Wikidata/QuickStatements: «label QS:Len,"..."», «title ...», склеенные дубли названия */ function cutWd(x){ x=String(x||''); x=x.replace(/(?:["'\u201c\u201d\s,;]*\b(?:label|title|description|alias|caption|date)\b)?\s*\bQS\s*:\s*[A-Za-z]+\d*\b[\s\S]*$/i,''); x=x.replace(/["'\u201c\u201d\s,;]*\b(?:label|description|alias|caption)\b\s*[:=]?\s*["'\u201c\u201d]?\s*[A-Z][\s\S]*$/,function(m){return /^["'\u201c\u201d\s,;]*\b(?:label|description|alias|caption)\b\s*[:=]/.test(m)?'':m}); /* «Названиеtitle Название» или «At the Cafétitle the Café»: после слова title идёт повтор названия или его конца */ var t=x.match(/^([\s\S]{3,}?)\s*title\s*[:=]?\s*["'\u201c\u201d]?\s*([\s\S]+)$/i); if(t){var A=t[1].replace(/\s+$/,''),B=t[2].replace(/["'\u201c\u201d\s,;]+$/,'').toLowerCase(),a=A.toLowerCase(); if(B.length>=3&&(a.indexOf(B)>-1||B.indexOf(a)===0))x=A} return x.replace(/["'\u201c\u201d\s,;]+$/,''); } /* очистка служебных символов и данных из названий, авторов и дат */ function clean(x){ x=cutWd(x); /* \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u044b\u0435 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b, \u0437\u0430\u043c\u0435\u043d\u0430-\u00ab\u0440\u043e\u043c\u0431\u0438\u043a\u00bb, \u043c\u044f\u0433\u043a\u0438\u0435 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044b, \u0441\u0442\u0440\u0435\u043b\u043a\u0438 \u0438 \u0433\u0435\u043e\u043c\u0435\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0437\u043d\u0430\u0447\u043a\u0438 */ x=x.replace(/[\u0000-\u001f\u007f-\u009f\u00ad\u200b-\u200f\u202a-\u202e\u2060\ufeff\ufffc\ufffd\u2190-\u21ff\u25a0-\u25ff\u2600-\u27bf]/g,' ').replace(/[\u00a0\u2000-\u200a\u202f\u205f\u3000]/g,' '); /* \u043e\u0441\u0442\u0430\u0442\u043a\u0438 HTML-\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u0438 \u0432\u0438\u043a\u0438-\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438: '''\u0436\u0438\u0440\u043d\u044b\u0439''' ''\u043a\u0443\u0440\u0441\u0438\u0432'' {{\u0448\u0430\u0431\u043b\u043e\u043d}} | * # ~ */ x=x.replace(/&(?:#\d+|#x[0-9a-f]+|[a-z]+);/gi,' ').replace(/'{2,}/g,'').replace(/[{}|*#~^`\\]/g,' '); x=x.replace(/https?:\/\/\S+/g,'').replace(/\[\[|\]\]|\{\{|\}\}/g,'').replace(/<[^>]*>/g,''); x=x.replace(/date\s*QS:\S*/gi,'').replace(/\bQS:P\d+\S*/gi,'').replace(/\bwikidata:?\s*Q\d+/gi,'').replace(/\bQ\d{3,}\b/g,''); x=x.replace(/\(?\s*Google Art Project\s*\)?/gi,'').replace(/Wikimedia Commons/gi,''); x=x.replace(/\b(Creator|Author|Artist|Title|Description|Source)\s*:\s*/gi,''); x=x.replace(/\bunknown author(\s*unknown author)*/gi,'').replace(/\(\s*\)|\[\s*\]/g,''); x=x.replace(/_/g,' ').replace(/\s+/g,' '); /* \u043f\u0443\u0441\u0442\u044b\u0435 \u0441\u043a\u043e\u0431\u043a\u0438 \u043f\u043e\u0441\u043b\u0435 \u0447\u0438\u0441\u0442\u043a\u0438, \u043f\u043e\u0432\u0442\u043e\u0440\u044b \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u0440\u0435\u043f\u0438\u043d\u0430\u043d\u0438\u044f, \u043f\u0440\u043e\u0431\u0435\u043b\u044b \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u044f\u0442\u043e\u0439 */ x=x.replace(/\(\s*[,;:\-\u2013\u2014.]*\s*\)/g,'').replace(/\s+([,;:.!?)])/g,'$1').replace(/([,;:])\1+/g,'$1').replace(/\.{4,}/g,'...').replace(/\s+/g,' '); x=x.replace(/^[\s\-\u2013\u2014,;:|\u00b7.)\]]+|[\s\-\u2013\u2014,;:|\u00b7(\[]+$/g,''); return x.trim(); } /* \u0430\u0432\u0442\u043e\u0440: \u043e\u0431\u0440\u0435\u0437\u0430\u0435\u043c \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0443 \u0438\u043d\u0444\u043e\u0431\u043e\u043a\u0441\u0430 Wikidata (\u0434\u0430\u0442\u044b \u0440\u043e\u0436\u0434\u0435\u043d\u0438\u044f, \u043c\u0435\u0441\u0442\u0430, \u043a\u043e\u0434\u044b ULAN/VIAF), \u043f\u0440\u043e\u0447\u0435\u0440\u043a\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u00ab\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d\u00bb */ function cleanArtist(x){ x=String(x||''); x=x.replace(/\b(?:Alternative names?|Description|Date of birth|Date of death|Location of birth|Location of death|Work location|Work period|Authority file|Genre|Gender|Occupation|Notable works?|Wikipedia|Wikidata)\b[\s\S]*$/i,''); x=x.replace(/\b(?:ULAN|VIAF|ISNI|GND|LCCN|RKD|SUDOC|BNF|WGA|Union List)\b\s*(?:ID)?\s*:?\s*[\w\-\/]*/gi,''); x=x.replace(/\b(?:attributed to|follower of|circle of|school of|workshop of|studio of|after|manner of)\s*$/i,''); x=clean(x); if(/^(?:unknown|anonymous|anonym|anon\.?|unidentified|uncredited|not identified|artist unknown|author unknown|no author|none|n\/a|\?+|-+)(?:\s+(?:artist|author|painter|master))?$/i.test(x))return ''; if(x.length>90)x=x.slice(0,90).replace(/[\s,;(\[][^\s,;(\[]*$/,'').replace(/[\s,;(\[\-\u2013\u2014]+$/,''); return x; } /* \u0434\u0430\u0442\u0430: \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0440\u0435\u043c\u044f ISO-\u0444\u043e\u0440\u043c\u0430\u0442\u0430, \u043a\u043e\u0434\u044b \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 Wikidata, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u0440\u043e\u0434\u0435 \u00abunknown\u00bb */ function cleanDate(x){ x=String(x||''); x=x.replace(/(\d{4}(?:-\d{2}){0,2})[T\s]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?/g,'$1'); x=x.replace(/(\d)\/\d{1,2}\b/g,'$1').replace(/\+(?=\d{4})/g,''); x=clean(x); if(/^(?:unknown|undated|n\.?\s?d\.?|date unknown|no date|not dated|\?+|-+)$/i.test(x))return ''; if(x.length>40)x=x.slice(0,40).replace(/[\s,;(\[][^\s,;(\[]*$/,'').replace(/[\s,;(\[\-\u2013\u2014]+$/,''); return x; } function cleanTitle(x){ x=String(x||'').replace(/^File:/i,'').replace(/\.(jpe?g|png|tiff?|gif|webp|svg)$/i,''); x=x.replace(/_/g,' '); x=cutWd(x); x=x.replace(/\(\s*[\d\s.,\-\/]+\)/g,''); x=x.replace(/\((?:[^)]*\b(?:inv|id|no|nr|cat|acc|accession|object|museum|wga|ref)\b[^)]*)\)/gi,''); x=x.replace(/\b(?:inv|no|nr|cat|acc)\.?\s*[A-Za-z]*[\-.]?\d[\w.\-\/]*/gi,''); x=x.replace(/\s[-\u2013\u2014]\s*[\d\s.,\-\/]+$/,''); x=x.replace(/\s+[-\u2013\u2014]\s*$/,''); /* \u0445\u0432\u043e\u0441\u0442\u044b \u0438\u043c\u0451\u043d \u0444\u0430\u0439\u043b\u043e\u0432: WGA12345, \u00abcropped\u00bb, \u00abedit\u00bb, \u043d\u043e\u043c\u0435\u0440\u0430 \u043a\u043e\u043f\u0438\u0439 (2), - 1 */ x=x.replace(/\bWGA\s*\d+\b/gi,''); x=x.replace(/\s*[-\u2013\u2014(]\s*(?:cropped?|uncropped|edit(?:ed)?|retouched|restored|hi-?res|high resolution|hd|scan(?:ned)?|colou?r corrected|full)\s*\)?\s*$/i,''); x=x.replace(/\s*\(\s*\d{1,2}\s*\)\s*$/,'').replace(/\s+[-\u2013\u2014]\s*\d{1,2}$/,''); return clean(x); } var COMMONS_Q=['incategory:"Featured pictures of paintings"','deepcategory:"Google Art Project works by artist"','incategory:"Quality images of paintings"']; function commonsUrl(q,offset){ return 'https://commons.wikimedia.org/w/api.php?action=query&format=json&origin=*&generator=search&gsrnamespace=6&gsrlimit=20&gsroffset='+offset+ '&gsrsearch='+encodeURIComponent(q+' filemime:jpeg')+'&prop=imageinfo&iiprop=url|mime|extmetadata&iiurlwidth=1000&iiextmetadatafilter=ObjectName|Artist|DateTimeOriginal|LicenseShortName'; } var SOURCES=[{name:'Wikimedia Commons',get:function(){ var q=pick(COMMONS_Q); function parse(d){ var pages=d.query&&d.query.pages?Object.keys(d.query.pages).map(function(k){return d.query.pages[k]}):[]; return pages.filter(function(p){return p.imageinfo&&p.imageinfo[0]&&p.imageinfo[0].thumburl}); } return getJSON(commonsUrl(q,rint(300))).then(function(d){ var ok=parse(d); if(ok.length)return ok; return getJSON(commonsUrl(q,0)).then(parse); }).then(function(ok){ if(!ok.length)throw new Error('empty'); var p=pick(ok),ii=p.imageinfo[0],m=ii.extmetadata||{}; function mv(k){return m[k]&&m[k].value?plain(m[k].value):''} var ttl=cleanTitle(mv('ObjectName'))||cleanTitle(p.title); return {title:ttl||'Без названия',artist:cleanArtist(mv('Artist'))||'Автор неизвестен',date:cleanDate(mv('DateTimeOriginal')),img:ii.thumburl,url:ii.descriptionurl,museum:'Wikimedia Commons',rights:mv('LicenseShortName')||'Открытая лицензия'}; }); }}]; function loadImg(src){return new Promise(function(res,rej){var im=new Image();im.onload=function(){res(im)};im.onerror=function(){rej(new Error('img '+src))};im.decoding='async';im.src=src})} /* одна картина: случайный музей, при ошибке следующий (до 6 попыток). Возвращает {a, im} */ function fetchOne(){ var order=SOURCES.slice().sort(function(){return Math.random()-.5}),tries=0; return new Promise(function(resolve,reject){ (function step(){ if(tries>=6){reject(new Error('no artwork'));return} var s=order[tries%order.length];tries++; s.get().then(function(a){ var list=a.imgs||[a.img],i=0; return (function t(){return loadImg(list[i]).catch(function(e){if(++i>=list.length)throw e;return t()})})().then(function(im){resolve({a:a,im:im})}); }).catch(step); })(); }); } /* inline !important: иначе правило страницы *{transition:none!important} при включённом «уменьшении движения» в ОС гасит анимацию */ var FADE=1000; /* плавное угасание прежней картины, мс */ function setTr(el,v){el.style.setProperty('transition',v,'important')} /* название в одну строку; если не помещается, оно плавно бегает туда-обратно */ function fitTitle(){ var el=cap.querySelector('.at'),inn=el&&el.firstChild;if(!inn)return; el.classList.remove('run');inn.style.removeProperty('animation'); /* десктоп: длинное название обрезается многоточием, бегущая строка только на мобильном */ if(!(window.matchMedia&&matchMedia('(max-width:600px)').matches))return; var d=inn.scrollWidth-el.clientWidth; if(d>2){inn.style.setProperty('--d',-d+'px');inn.style.setProperty('animation','art-run '+Math.max(4,d/35+3)+'s ease-in-out infinite alternate','important');el.classList.add('run')} } window.addEventListener('resize',fitTitle); if(document.fonts&&document.fonts.ready)document.fonts.ready.then(fitTitle); var cst=document.createElement('style'); cst.textContent='.art-cap .at{white-space:nowrap;overflow:hidden}.art-cap .at-in{display:block;overflow:hidden;text-overflow:ellipsis}'+ '@media (max-width:600px){.art-cap .at-in{display:inline-block;overflow:visible;text-overflow:clip}}'+ '@keyframes art-run{0%,15%{transform:translateX(0)}85%,100%{transform:translateX(var(--d))}}'+ '@media (max-width:600px){.art-cap .as a{display:block;margin-top:4px}.art-cap .as-sep{display:none}}'; document.head.appendChild(cst); /* показ с плавной сменой: новый слайд проявляется поверх старого */ function show(a,im){ im.alt=a.title+(a.artist?', '+a.artist:''); var sl=document.createElement('div');sl.className='art-slide';sl.appendChild(im);setTr(sl,'opacity '+FADE+'ms ease'); var w=frame.querySelector('.art-wait');if(w)w.remove(); var old=frame.querySelectorAll('.art-slide'); frame.appendChild(sl);frame.setAttribute('aria-busy','false'); curImg=im;im.draggable=false;resetView(); requestAnimationFrame(function(){requestAnimationFrame(function(){sl.classList.add('on');if(zoomOn)armZoom(im)})}); Array.prototype.forEach.call(old,function(o){setTr(o,'opacity '+FADE+'ms ease');o.classList.remove('on');setTimeout(function(){o.remove()},FADE+150)}); var by=[a.artist,a.date].filter(Boolean).map(esc).join(', '); cap.innerHTML='
'+esc(a.title||'Без названия')+'
'+(by?''+by+'
':'')+ ''; requestAnimationFrame(fitTitle); if(srcEl)srcEl.textContent=a.museum; } /* ---------- слайдшоу: смена раз в 5 секунд, следующая картина подгружается заранее ---------- */ var DELAY=5000,timer=0,paused=false,onScreen=false,busy=false,shown=false,nextP=null,started=false; var pauseBtn=document.getElementById('art-pause'); var zoomBtn=document.getElementById('art-zoom'),zoomOn=true,curImg=null,ZOOM=1.5; if(!zoomBtn&&pauseBtn&&pauseBtn.parentNode){ /* если в index.html кнопки нет (старая версия страницы), создаём её здесь */ zoomBtn=document.createElement('button'); zoomBtn.type='button';zoomBtn.id='art-zoom';zoomBtn.className='share art-zoom'; zoomBtn.setAttribute('aria-pressed','false');zoomBtn.setAttribute('aria-label','Плавное увеличение картины');zoomBtn.title='Плавное увеличение'; zoomBtn.innerHTML=''; pauseBtn.parentNode.insertBefore(zoomBtn,pauseBtn.nextSibling); var st=document.createElement('style'); st.textContent='.art-zoom{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.horo .share[aria-pressed="true"]{border-color:var(--accent);color:var(--accent)}'; document.head.appendChild(st); } if(zoomBtn)zoomBtn.setAttribute('aria-pressed',zoomOn?'true':'false'); /* лупа включена по умолчанию */ function startZoom(im){if(!im)return;setTr(im,'transform '+DELAY+'ms linear');im.style.transform='scale('+ZOOM+')'} var zoomT=0; function armZoom(im){clearTimeout(zoomT);zoomT=setTimeout(function(){if(zoomOn&&im===curImg)startZoom(im)},DELAY)} function stopZoom(im){clearTimeout(zoomT);if(!im)return;view={s:1,x:0,y:0};frame.style.touchAction='';setTr(im,'transform .6s ease');im.style.transform='scale(1)'} var slowBtn=document.getElementById('art-slower'); /* ---------- ручной просмотр: перетаскивание пальцем или курсором + двойной клик/тап ---------- Пока картину двигают, автосмена стоит. После отпускания до следующей картины проходит ровно EXTRA (5 с). */ var EXTRA=5000,hold=false,drag=null,view={s:1,x:0,y:0}; function curScale(im){var m=getComputedStyle(im).transform;if(!m||m==='none')return 1;var v=m.match(/matrix\(([^)]+)\)/);return v?parseFloat(v[1])||1:1} function applyView(im,anim){ setTr(im,anim?'transform .3s ease':'none'); im.style.transform='translate('+view.x+'px,'+view.y+'px) scale('+view.s+')'; /* когда картина увеличена, жесты на рамке уходят в перетаскивание, а не в прокрутку страницы */ frame.style.touchAction=view.s>1.05?'none':''; } function clampView(im){ var sl=im.parentNode,mx=Math.max(0,(im.offsetWidth*view.s-sl.clientWidth)/2),my=Math.max(0,(im.offsetHeight*view.s-sl.clientHeight)/2); view.x=Math.max(-mx,Math.min(mx,view.x));view.y=Math.max(-my,Math.min(my,view.y)); } function resetView(){view={s:1,x:0,y:0};drag=null;hold=false;frame.style.touchAction=''} /* остановить автоувеличение на текущем масштабе и взять управление на себя */ function takeOver(im){ hold=true;clearTimeout(timer);clearTimeout(zoomT); if(view.s===1)view.s=curScale(im); } function release(){ if(!hold)return; hold=false;schedule(EXTRA); } frame.addEventListener('pointerdown',function(e){ if(!curImg||(e.pointerType==='mouse'&&e.button!==0))return; drag={id:e.pointerId,x:e.clientX,y:e.clientY,x0:e.clientX,y0:e.clientY,moved:false}; }); frame.addEventListener('pointermove',function(e){ if(!drag||e.pointerId!==drag.id||!curImg)return; if(!drag.moved){ if(Math.hypot(e.clientX-drag.x0,e.clientY-drag.y0)<5)return; drag.moved=true;takeOver(curImg); if(view.s<1.5)view.s=2; /* если картина ещё не увеличена, при движении приближаем её */ try{frame.setPointerCapture(e.pointerId)}catch(x){} frame.classList.add('art-grab'); } view.x+=e.clientX-drag.x;view.y+=e.clientY-drag.y; drag.x=e.clientX;drag.y=e.clientY; clampView(curImg);applyView(curImg,false); }); function endDrag(e){ if(!drag||e.pointerId!==drag.id)return; var was=drag.moved;drag=null;frame.classList.remove('art-grab'); if(was)release(); } frame.addEventListener('pointerup',endDrag); frame.addEventListener('pointercancel',endDrag); frame.addEventListener('dblclick',function(e){ if(!curImg)return; var im=curImg;takeOver(im); if(view.s>1.05){view={s:1,x:0,y:0}} else{ var r=im.getBoundingClientRect(),cx=r.left+r.width/2,cy=r.top+r.height/2,k=2.5; view.s=k;view.x=-(e.clientX-cx)*(k-1);view.y=-(e.clientY-cy)*(k-1);clampView(im); } applyView(im,true);release(); }); frame.addEventListener('dragstart',function(e){e.preventDefault()}); var gst=document.createElement('style'); gst.textContent='.art-frame{touch-action:pan-y pinch-zoom;cursor:grab;-webkit-user-select:none;user-select:none}.art-frame.art-grab{cursor:grabbing}'; document.head.appendChild(gst); function prefetch(){ if(nextP)return; var p=fetchOne();nextP=p; p.catch(function(){if(nextP===p)nextP=null}); } var ZOOM_END=2000; /* пауза после окончания увеличения, затем картина плавно гаснет, мс */ function schedule(exact){ clearTimeout(timer); if(paused||!onScreen||document.hidden||hold)return; timer=setTimeout(advance,exact||(zoomOn?DELAY*2+ZOOM_END:DELAY)); } function advance(){ clearTimeout(timer); if(busy)return; busy=true;btn.disabled=true; var p=nextP||fetchOne();nextP=null; p.then(function(r){ show(r.a,r.im);shown=true;busy=false;btn.disabled=false; prefetch();schedule(); },function(){ busy=false;btn.disabled=false; if(!shown){frame.innerHTML='Не удалось загрузить картину. Попробуйте ещё раз.';frame.setAttribute('aria-busy','false');cap.innerHTML=''} schedule(); }); } var mobMQ=window.matchMedia?matchMedia('(max-width:600px)'):null; function updSlow(){ if(!slowBtn)return; var mob=mobMQ&&mobMQ.matches; slowBtn.textContent=mob?'+5 с':'Медленнее: '+(DELAY/1000)+' с'; slowBtn.title='Смена картины каждые '+(DELAY/1000)+' с'; } updSlow(); if(mobMQ){if(mobMQ.addEventListener)mobMQ.addEventListener('change',updSlow);else if(mobMQ.addListener)mobMQ.addListener(updSlow)} if(slowBtn)slowBtn.addEventListener('click',function(){ DELAY=DELAY>=15000?5000:DELAY+5000;updSlow(); if(!paused)schedule(); }); if(zoomBtn)zoomBtn.addEventListener('click',function(){ zoomOn=!zoomOn; zoomBtn.setAttribute('aria-pressed',zoomOn?'true':'false'); if(zoomOn)armZoom(curImg);else stopZoom(curImg); if(!paused)schedule(); }); btn.addEventListener('click',advance); if(pauseBtn)pauseBtn.addEventListener('click',function(){ paused=!paused; pauseBtn.textContent=paused?'Запустить':'Пауза'; pauseBtn.setAttribute('aria-pressed',paused?'true':'false'); if(paused)clearTimeout(timer);else schedule(); }); document.addEventListener('visibilitychange',function(){if(document.hidden)clearTimeout(timer);else schedule()}); /* запуск после полной загрузки страницы; работает, пока плитка видна на экране */ function arm(){ var tile=document.getElementById('art'); function enter(){onScreen=true;if(!started){started=true;advance()}else schedule()} function leave(){onScreen=false;clearTimeout(timer)} if('IntersectionObserver' in window){ new IntersectionObserver(function(e){if(e[0].isIntersecting)enter();else leave()},{rootMargin:'200px'}).observe(tile); }else enter(); } if(document.readyState==='complete')arm();else window.addEventListener('load',arm); })();