/* ---------- случайная картина из открытых коллекций музеев (CC0 / общественное достояние, без ключей) ---------- */ (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)]} /* таймаут 8 с: зависший запрос (чаще всего SPARQL Викиданных) раньше блокировал смену картины навсегда */ function getJSON(u){ var ac=window.AbortController?new AbortController():null,t=setTimeout(function(){if(ac)ac.abort()},8000); return fetch(u,ac?{signal:ac.signal}:undefined).then(function(r){if(!r.ok)throw new Error(r.status+' '+u);return r.json()}).then(function(j){clearTimeout(t);return j},function(e){clearTimeout(t);throw e}); } var metIds=null; /* каждая функция возвращает {title,artist,date,img,url,museum,rights} или бросает ошибку */ var SOURCES=[ {name:'Art Institute of Chicago',get:function(){ var p=1+rint(25); return getJSON('https://api.artic.edu/api/v1/artworks/search?q=oil%20painting&query[term][is_public_domain]=true&fields=id,title,artist_display,date_display,image_id&limit=40&page='+p).then(function(d){ var ok=(d.data||[]).filter(function(a){return a.image_id}); if(!ok.length)throw new Error('empty'); var a=pick(ok); return {title:a.title,artist:(a.artist_display||'').split('\n')[0],date:a.date_display,img:'https://www.artic.edu/iiif/2/'+a.image_id+'/full/843,/0/default.jpg',url:'https://www.artic.edu/artworks/'+a.id,museum:'Художественный институт Чикаго',rights:'Общественное достояние (CC0)'}; }); }}, {name:'The Met',get:function(){ var ids=metIds?Promise.resolve(metIds):getJSON('https://collectionapi.metmuseum.org/public/collection/v1/search?departmentId=11&hasImages=true&isPublicDomain=true&q=painting').then(function(d){metIds=d.objectIDs||[];return metIds}); return ids.then(function(list){ if(!list.length)throw new Error('empty'); return getJSON('https://collectionapi.metmuseum.org/public/collection/v1/objects/'+pick(list)); }).then(function(o){ if(!o.primaryImageSmall||!o.isPublicDomain)throw new Error('no image'); return {title:o.title,artist:o.artistDisplayName||'Автор неизвестен',date:o.objectDate,img:o.primaryImageSmall,url:o.objectURL,museum:'Метрополитен-музей, Нью-Йорк',rights:'Общественное достояние (CC0)'}; }); }}, {name:'Cleveland Museum of Art',get:function(){ return getJSON('https://openaccess-api.clevelandart.org/api/artworks/?type=Painting&has_image=1&cc0=1&limit=20&skip='+rint(500)).then(function(d){ var ok=(d.data||[]).filter(function(a){return a.images&&a.images.web&&a.images.web.url}); if(!ok.length)throw new Error('empty'); var a=pick(ok),cr=(a.creators&&a.creators[0]&&a.creators[0].description)||''; return {title:a.title,artist:cr.replace(/\s*\(.*$/,'')||'Автор неизвестен',date:a.creation_date,img:a.images.web.url,url:a.url,museum:'Художественный музей Кливленда',rights:'Общественное достояние (CC0)'}; }); }} ]; /* Europeana: нужен бесплатный ключ https://pro.europeana.eu/page/get-api (вставьте сюда). Пока пусто, источник пропускается. */ var EUROPEANA_KEY='rkleclifer'; function rightsLabel(u){ u=String(u||''); if(u.indexOf('publicdomain/zero')>-1)return 'CC0'; if(u.indexOf('publicdomain/mark')>-1)return 'Общественное достояние'; var m=u.match(/licenses\/([a-z-]+)\//); return m?'CC '+m[1].toUpperCase():'Открытая лицензия'; } if(EUROPEANA_KEY)SOURCES.push({name:'Europeana',get:function(){ var u='https://api.europeana.eu/record/v2/search.json?wskey='+encodeURIComponent(EUROPEANA_KEY)+'&query=painting&qf=TYPE:IMAGE&qf=MEDIA:true&reusability=open&profile=standard&rows=30&start='+(1+rint(900)); return getJSON(u).then(function(d){ var ok=(d.items||[]).filter(function(a){return a.edmIsShownBy&&a.edmIsShownBy[0]||a.edmPreview&&a.edmPreview[0]}); if(!ok.length)throw new Error('empty'); var a=pick(ok),imgs=[]; if(a.edmIsShownBy&&a.edmIsShownBy[0])imgs.push(a.edmIsShownBy[0]); if(a.edmPreview&&a.edmPreview[0])imgs.push(a.edmPreview[0]); return {title:(a.title&&a.title[0])||'Без названия',artist:(a.dcCreator&&a.dcCreator[0])||'Автор неизвестен',date:(a.year&&a.year[0])||'',imgs:imgs,url:a.guid,museum:((a.dataProvider&&a.dataProvider[0])||'Europeana')+' · Europeana',rights:rightsLabel(a.rights&&a.rights[0])}; }); }}); /* SMK (Национальная галерея Дании): открытый API без ключа, только картины в общественном достоянии с изображением */ var smkTotal=null; function smkUrl(offset,rows){ return 'https://api.smk.dk/api/v1/art/search/?keys='+encodeURIComponent('*')+'&filters='+encodeURIComponent('[has_image:true],[public_domain:true],[object_names:maleri]')+'&offset='+offset+'&rows='+rows+'&lang=en'; } SOURCES.push({name:'SMK',get:function(){ var tot=smkTotal!=null?Promise.resolve(smkTotal):getJSON(smkUrl(0,1)).then(function(d){smkTotal=d.found||0;return smkTotal}); return tot.then(function(n){ if(!n)throw new Error('empty'); return getJSON(smkUrl(rint(Math.max(1,n-20)),20)); }).then(function(d){ var ok=(d.items||[]).filter(function(a){return a.image_thumbnail}); if(!ok.length)throw new Error('empty'); var a=pick(ok),pr=(a.production&&a.production[0])||{}; var who=((pr.creator_forename||'')+' '+(pr.creator_surname||'')).trim()||pr.creator||'Автор неизвестен'; var t=(a.titles&&a.titles[0]&&a.titles[0].title)||'Без названия'; return {title:t,artist:who,date:(a.production_date&&a.production_date[0]&&a.production_date[0].period)||'',img:a.image_thumbnail,url:'https://open.smk.dk/artwork/image/'+encodeURIComponent(a.object_number),museum:'Национальная галерея Дании (SMK)',rights:'Общественное достояние (CC0)'}; }); }}); /* Wikimedia Commons: поиск файлов в категориях с картинами, ключ не нужен (origin=* включает CORS) */ function plain(h){return new DOMParser().parseFromString(String(h||''),'text/html').body.textContent.replace(/\s+/g,' ').trim()} var COMMONS_Q=['incategory:"Featured pictures of paintings"','deepcategory:"Google Art Project works by artist"']; 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'; } SOURCES.push({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):''} return {title:mv('ObjectName')||p.title.replace(/^File:/,'').replace(/\.[a-z]+$/i,''),artist:mv('Artist')||'Автор неизвестен',date:mv('DateTimeOriginal'),img:ii.thumburl,url:ii.descriptionurl,museum:'Wikimedia Commons',rights:mv('LicenseShortName')||'Открытая лицензия'}; }); }}); /* Rijksmuseum (Амстердам): новый открытый API без ключа (Linked Art + IIIF), все изображения в общественном достоянии. Документация: https://data.rijksmuseum.nl/docs Поиск отдаёт только идентификаторы, поэтому на одну картину идут запросы: поиск, объект, VisualItem, DigitalObject. Поиск идёт по списку известных художников: так имя автора известно заранее и не нужно разбирать ответ. */ var RIJKS_ARTISTS=['Rembrandt van Rijn','Johannes Vermeer','Frans Hals','Jan Steen','Jacob van Ruisdael','Pieter de Hooch','Gerard ter Borch','Vincent van Gogh','Willem Kalf','Meindert Hobbema','Aelbert Cuyp','Jan van de Cappelle','Hendrick Avercamp','Gerrit Dou','Pieter Saenredam','Isaac Israels','George Hendrik Breitner','Jozef Israëls','Johan Barthold Jongkind','Ferdinand Bol','Judith Leyster','Adriaen van Ostade','Jan van Goyen','Salomon van Ruysdael','Paulus Potter','Nicolaes Maes','Jan Lievens','Hendrick ter Brugghen','Carel Fabritius','Willem van de Velde']; var rijksCache={}; function rijksData(u){return String(u||'').replace('id.rijksmuseum.nl','data.rijksmuseum.nl')} function rijksRec(id){return getJSON(rijksData(id)+(String(id).indexOf('?')>-1?'':'?_profile=la-framed'))} function rijksFirst(arr,key){var x=arr&&arr[0];return x&&x[key||'id']} function rijksTitle(o){ var names=(o.identified_by||[]).filter(function(n){return n.type==='Name'&&n.content}); function lang(n,id){return (n.language||[]).some(function(l){return l.id&&l.id.indexOf(id)>-1})} function prim(n){return (n.classified_as||[]).some(function(c){return c.id&&c.id.indexOf('300404670')>-1})} var best=names.filter(function(n){return prim(n)&&lang(n,'300388277')})[0]||names.filter(function(n){return lang(n,'300388277')})[0]||names.filter(prim)[0]||names[0]; return best?best.content:''; } function rijksDate(o){ try{var t=o.produced_by.timespan,n=(t.identified_by||[]).filter(function(x){return x.content})[0];return n?n.content:''}catch(e){return ''} } SOURCES.push({name:'Rijksmuseum',get:function(){ var artist=pick(RIJKS_ARTISTS); var list=rijksCache[artist]?Promise.resolve(rijksCache[artist]):getJSON('https://data.rijksmuseum.nl/search/collection?type=painting&imageAvailable=true&creator='+encodeURIComponent(artist)).then(function(d){ return rijksCache[artist]=(d.orderedItems||[]).map(function(i){return i.id}); }); var objId,obj; return list.then(function(ids){ if(!ids.length)throw new Error('empty'); objId=pick(ids); return rijksRec(objId); }).then(function(o){ obj=o; var vi=rijksFirst(o.shows); if(!vi)throw new Error('no visual item'); return rijksRec(vi); }).then(function(v){ var dobj=rijksFirst(v.digitally_shown_by); if(!dobj)throw new Error('no digital object'); return rijksRec(dobj); }).then(function(dg){ var ap=(dg.access_point||[]).map(function(x){return x.id}).filter(function(u){return u&&u.indexOf('iiif.micr.io/')>-1})[0]; if(!ap)throw new Error('no iiif'); var iid=ap.split('iiif.micr.io/')[1].split('/')[0]; return {title:rijksTitle(obj)||'Без названия',artist:artist,date:rijksDate(obj),img:'https://iiif.micr.io/'+iid+'/full/1600,/0/default.jpg',url:String(objId).replace('data.rijksmuseum.nl','id.rijksmuseum.nl'),museum:'Рейксмузей, Амстердам',rights:'Общественное достояние (CC0)'}; }); }}); /* ---------- восточное искусство ---------- */ var BASE_MET='https://collectionapi.metmuseum.org/public/collection/v1/'; var metEast={}; SOURCES.push({name:'The Met · Азия и ислам',get:function(){ var dep=pick([6,14]); /* 6 = Asian Art, 14 = Islamic Art */ var ids=metEast[dep]?Promise.resolve(metEast[dep]):getJSON(BASE_MET+'search?departmentId='+dep+'&hasImages=true&isPublicDomain=true&q=painting').then(function(d){return metEast[dep]=d.objectIDs||[]}); return ids.then(function(list){ if(!list.length)throw new Error('empty'); return getJSON(BASE_MET+'objects/'+pick(list)); }).then(function(o){ if(!o.primaryImageSmall||!o.isPublicDomain)throw new Error('no image'); return {title:o.title,artist:o.artistDisplayName||o.culture||'Автор неизвестен',date:o.objectDate,img:o.primaryImageSmall,url:o.objectURL,museum:'Метрополитен-музей, '+(o.department||'Азиатское искусство'),rights:'Общественное достояние (CC0)'}; }); }}); var clevTot={}; SOURCES.push({name:'Cleveland · Восток',get:function(){ var dep=pick(['Chinese Art','Japanese Art','Korean Art','Indian and Islamic Art']); var base='https://openaccess-api.clevelandart.org/api/artworks/?type=Painting&has_image=1&cc0=1&department='+encodeURIComponent(dep); var tot=clevTot[dep]!=null?Promise.resolve(clevTot[dep]):getJSON(base+'&limit=1').then(function(d){return clevTot[dep]=(d.info&&d.info.total)||0}); return tot.then(function(n){ if(!n)throw new Error('empty'); return getJSON(base+'&limit=20&skip='+rint(Math.max(1,n-20))); }).then(function(d){ var ok=(d.data||[]).filter(function(a){return a.images&&a.images.web&&a.images.web.url}); if(!ok.length)throw new Error('empty'); var a=pick(ok),cr=(a.creators&&a.creators[0]&&a.creators[0].description)||''; return {title:a.title,artist:cr.replace(/\s*\(.*$/,'')||'Автор неизвестен',date:a.creation_date,img:a.images.web.url,url:a.url,museum:'Художественный музей Кливленда, '+dep,rights:'Общественное достояние (CC0)'}; }); }}); var aicAsiaTot=null; function aicAsiaUrl(page,limit){ return 'https://api.artic.edu/api/v1/artworks/search?q=painting&query[term][is_public_domain]=true&query[term][department_title]='+encodeURIComponent('Arts of Asia')+'&fields=id,title,artist_display,date_display,image_id&limit='+limit+'&page='+page; } SOURCES.push({name:'Art Institute · Азия',get:function(){ var tot=aicAsiaTot!=null?Promise.resolve(aicAsiaTot):getJSON(aicAsiaUrl(1,1)).then(function(d){return aicAsiaTot=(d.pagination&&d.pagination.total)||0}); return tot.then(function(n){ if(!n)throw new Error('empty'); return getJSON(aicAsiaUrl(1+rint(Math.max(1,Math.floor(n/40))),40)); }).then(function(d){ var ok=(d.data||[]).filter(function(a){return a.image_id}); if(!ok.length)throw new Error('empty'); var a=pick(ok); return {title:a.title,artist:(a.artist_display||'').split('\n')[0]||'Автор неизвестен',date:a.date_display,img:'https://www.artic.edu/iiif/2/'+a.image_id+'/full/843,/0/default.jpg',url:'https://www.artic.edu/artworks/'+a.id,museum:'Художественный институт Чикаго, искусство Азии',rights:'Общественное достояние (CC0)'}; }); }}); /* V&A (Лондон): сильная азиатская коллекция, ключ не нужен; изображения V&A обычно для некоммерческого использования */ var VA_Q=['chinese painting','japanese painting','japanese woodblock print','indian painting','persian miniature','mughal painting','korean painting']; SOURCES.push({name:'V&A · Азия',get:function(){ return getJSON('https://api.vam.ac.uk/v2/objects/search?q='+encodeURIComponent(pick(VA_Q))+'&images_exist=1&page_size=30&page='+(1+rint(8))).then(function(d){ var ok=(d.records||[]).filter(function(a){return a._primaryImageId}); if(!ok.length)throw new Error('empty'); var a=pick(ok); return {title:a._primaryTitle||(a._objectType)||'Без названия',artist:(a._primaryMaker&&a._primaryMaker.name)||'Автор неизвестен',date:a._primaryDate||'',img:'https://framemark.vam.ac.uk/collections/'+a._primaryImageId+'/full/843,/0/default.jpg',url:'https://collections.vam.ac.uk/item/'+a.systemNumber+'/',museum:'Музей Виктории и Альберта, Лондон',rights:'Открытый доступ, некоммерческое использование'}; }); }}); /* ---------- лучшие музеи мира через Викиданные ---------- У Лувра, Эрмитажа, Прадо и других нет открытого поискового API, который можно вызвать из браузера без ключа. Поэтому берём картины из Викиданных: у работы указан музей-владелец (P195) и свободное изображение на Commons (P18). SPARQL-эндпоинт query.wikidata.org отдаёт CORS, ключ не нужен. Идентификаторы музеев проверены по страницам Commons. Добавить музей: вписать его Q-номер Викиданных и название. */ var WD_MUSEUMS=[ {q:'Q19675',name:'Лувр, Париж'}, {q:'Q132783',name:'Эрмитаж, Санкт-Петербург'}, {q:'Q160112',name:'Прадо, Мадрид'}, {q:'Q51252',name:'Галерея Уффици, Флоренция'}, {q:'Q180788',name:'Национальная галерея, Лондон'}, {q:'Q23402',name:'Музей Орсе, Париж'}, {q:'Q183334',name:'Третьяковская галерея, Москва'}, {q:'Q211583',name:'Русский музей, Санкт-Петербург'}, {q:'Q95569',name:'Музей истории искусств, Вена'}, {q:'Q182955',name:'Музеи Ватикана'}, /* без номера Q: он находится поиском по названию s при первом обращении (см. wdResolve) */ {s:'Mauritshuis',name:'Маурицхёйс, Гаага'}, {s:'Van Gogh Museum',name:'Музей Ван Гога, Амстердам'}, {s:'Kröller-Müller Museum',name:'Музей Крёллер-Мюллер, Оттерло'}, {s:'Alte Pinakothek',name:'Старая пинакотека, Мюнхен'}, {s:'Gemäldegalerie Berlin',name:'Картинная галерея, Берлин'}, {s:'Gemäldegalerie Alte Meister',name:'Галерея старых мастеров, Дрезден'}, {s:'Belvedere Vienna',name:'Бельведер, Вена'}, {s:'Museo Thyssen-Bornemisza',name:'Музей Тиссена-Борнемисы, Мадрид'}, {s:'Galleria Borghese',name:'Галерея Боргезе, Рим'}, {s:'Galleria dell\'Accademia Florence',name:'Галерея Академии, Флоренция'}, {s:'Pinacoteca di Brera',name:'Пинакотека Брера, Милан'}, {s:'Nationalmuseum Stockholm',name:'Национальный музей, Стокгольм'}, {s:'Ateneum',name:'Атенеум, Хельсинки'}, {s:'Museum of Fine Arts Budapest',name:'Музей изящных искусств, Будапешт'}, {s:'Musée de l\'Orangerie',name:'Музей Оранжери, Париж'}, {s:'Wallace Collection',name:'Коллекция Уоллеса, Лондон'}, {s:'Scottish National Gallery',name:'Национальная галерея Шотландии, Эдинбург'}, {s:'Pushkin State Museum of Fine Arts',name:'Музей Пушкина, Москва'}, {s:'Kunsthaus Zürich',name:'Кунстхаус, Цюрих'}, {s:'Kunstmuseum Basel',name:'Художественный музей, Базель'}, {s:'National Gallery of Art',name:'Национальная галерея искусства, Вашингтон'}, {s:'Museum of Fine Arts, Boston',name:'Музей изящных искусств, Бостон'}, {s:'Philadelphia Museum of Art',name:'Художественный музей, Филадельфия'}, {s:'J. Paul Getty Museum',name:'Музей Гетти, Лос-Анджелес'}, {s:'National Palace Museum',name:'Национальный дворцовый музей, Тайбэй'}, {s:'Tokyo National Museum',name:'Национальный музей, Токио'} ]; /* номер Q по названию: берём первый результат, у которого в описании есть музей/галерея/коллекция */ function wdResolve(m){ if(m.q)return Promise.resolve(m.q); return getJSON('https://www.wikidata.org/w/api.php?action=wbsearchentities&format=json&origin=*&language=en&uselang=en&type=item&limit=7&search='+encodeURIComponent(m.s)).then(function(d){ var r=((d&&d.search)||[]).filter(function(x){return /museum|gallery|pinacoth|collection/i.test(x.description||'')})[0]; if(!r)throw new Error('no wikidata id for '+m.s); return m.q=r.id; }); } var wdTot={}; function wdSparql(q){return getJSON('https://query.wikidata.org/sparql?format=json&query='+encodeURIComponent(q))} function wdCore(m){return '?item wdt:P195 wd:'+m.q+'; wdt:P31 wd:Q3305213; wdt:P18 ?i.'} function wdVal(b,k){return (b&&b[k]&&b[k].value)||''} function wdYear(v){var x=String(v||'').match(/^(-?\d{1,4})-/),y=x?parseInt(x[1],10):0;return y>0?String(y):''} function wdGet(m){ var tot=wdResolve(m).then(function(){ return wdTot[m.q]!=null?wdTot[m.q]:wdSparql('SELECT (COUNT(DISTINCT ?item) AS ?n) WHERE { '+wdCore(m)+' }').then(function(d){ return wdTot[m.q]=parseInt(wdVal(d.results.bindings[0],'n'),10)||0; }); }); return tot.then(function(n){ if(!n)throw new Error('empty'); return wdSparql('SELECT ?item ?itemLabel ?creatorLabel ?date ?img WHERE { '+ '{ SELECT ?item (SAMPLE(?i) AS ?img) WHERE { '+wdCore(m)+' } GROUP BY ?item ORDER BY ?item LIMIT 20 OFFSET '+rint(Math.max(1,n-20))+' } '+ 'OPTIONAL { ?item wdt:P170 ?creator } OPTIONAL { ?item wdt:P571 ?date } '+ 'SERVICE wikibase:label { bd:serviceParam wikibase:language "ru,en,mul,fr,es,it,de". } }'); }).then(function(d){ /* без подписи Викиданные отдают сам номер Q…: такие работы пропускаем */ var rows=((d.results&&d.results.bindings)||[]).filter(function(b){var t=wdVal(b,'itemLabel');return wdVal(b,'img')&&t&&!/^Q\d+$/.test(t)}); if(!rows.length)throw new Error('empty'); var b=pick(rows),img=wdVal(b,'img').replace(/^http:/,'https:'),cr=wdVal(b,'creatorLabel'); if(!cr||/^Q\d+$/.test(cr))cr='Автор неизвестен'; return {title:wdVal(b,'itemLabel'),artist:cr,date:wdYear(wdVal(b,'date')), img:img+(img.indexOf('?')>-1?'&':'?')+'width=1600', /* Special:FilePath отдаёт уменьшенную копию, а не оригинал на десятки мегабайт */ url:'https://www.wikidata.org/wiki/'+wdVal(b,'item').replace(/^.*\/(Q\d+)$/,'$1'),linkText:'Страница на Викиданных', museum:m.name,rights:'Wikimedia Commons, свободная лицензия'}; }); } WD_MUSEUMS.forEach(function(m){SOURCES.push({name:m.name,get:function(){return wdGet(m)}})}); /* ---------- музеи с ключом API (бесплатный; пока ключ пуст, источник пропускается) ---------- */ /* Harvard Art Museums: форма https://harvardartmuseums.org/collections/api */ var HARVARD_KEY=''; if(HARVARD_KEY)SOURCES.push({name:'Harvard',get:function(){ return getJSON('https://api.harvardartmuseums.org/object?apikey='+encodeURIComponent(HARVARD_KEY)+'&classification=Paintings&hasimage=1&imagepermissionlevel=0&size=30&page='+(1+rint(40))+'&fields=title,dated,people,primaryimageurl,url').then(function(d){ var ok=(d.records||[]).filter(function(a){return a.primaryimageurl}); if(!ok.length)throw new Error('empty'); var a=pick(ok); return {title:a.title,artist:(a.people&&a.people[0]&&a.people[0].name)||'Автор неизвестен',date:a.dated||'',img:a.primaryimageurl+'?height=1200&width=1600',url:a.url,museum:'Музеи искусств Гарварда',rights:'Открытый доступ'}; }); }}); /* Smithsonian Open Access: ключ https://api.data.gov/signup/ */ var SMITH_KEY=''; if(SMITH_KEY)SOURCES.push({name:'Smithsonian',get:function(){ return getJSON('https://api.si.edu/openaccess/api/v1.0/search?api_key='+encodeURIComponent(SMITH_KEY)+'&q='+encodeURIComponent('painting AND online_media_type:"Images" AND unit_code:"NPG" OR unit_code:"SAAM" OR unit_code:"FSG" OR unit_code:"HMSG"')+'&rows=30&start='+rint(2000)).then(function(d){ var ok=((d.response&&d.response.rows)||[]).filter(function(r){ var m=r.content&&r.content.descriptiveNonRepeating&&r.content.descriptiveNonRepeating.online_media; return m&&m.media&&m.media[0]&&m.media[0].content&&m.media[0].usage&&m.media[0].usage.access==='CC0'; }); if(!ok.length)throw new Error('empty'); var r=pick(ok),dn=r.content.descriptiveNonRepeating,fr=r.content.freetext||{}; var who=(fr.name&&fr.name[0]&&fr.name[0].content)||'Автор неизвестен',dt=(fr.date&&fr.date[0]&&fr.date[0].content)||''; return {title:(dn.title&&dn.title.content)||'Без названия',artist:who,date:dt,img:dn.online_media.media[0].content,url:dn.record_link,museum:'Смитсоновский институт, '+(dn.data_source||''),rights:'Общественное достояние (CC0)'}; }); }}); function loadImg(src){return new Promise(function(res,rej){ var im=new Image(),t=setTimeout(function(){im.onload=im.onerror=null;im.src='';rej(new Error('img timeout '+src))},12000); im.onload=function(){clearTimeout(t);res(im)};im.onerror=function(){clearTimeout(t);rej(new Error('img '+src))};im.decoding='async';im.src=src})} /* одна картина: случайный музей, при ошибке следующий (до 6 попыток). Возвращает {a, im} */ function fetchRaw(){ 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); })(); }); } /* любая загрузка следующей картины (фоновая или по кнопке) включает белое кольцо на кнопке увеличения */ function fetchOne(){ pend++;updLoad(); var p=fetchRaw(); function dec(){pend--;updLoad()} p.then(dec,dec); return p; } /* inline !important: иначе правило страницы *{transition:none!important} при включённом «уменьшении движения» в ОС гасит анимацию */ var FADE=1000; /* плавное угасание прежней картины, мс */ function setTr(el,v){el.style.setProperty('transition',v,'important')} /* очистка названия: убираем имена файлов, инвентарные номера, технику, скобки с кодами, лишние символы; длинное обрезаем */ var TECH='oil on [a-z ]+|oil and [a-z ]+|tempera|watercolou?r|gouache|acrylic|ink|woodblock|hanging scroll|handscroll|album leaf|folio|recto|verso|detail|digital|photograph|photo|scan|inv|inventory|accession|object|collection|museum|cm|mm|jpe?g|png|tiff?|google art project|wikimedia commons|public domain|cc0|wga|nga|rp-[a-z]|sk-[a-z]'; var TECH_RE=new RegExp('(^|[^a-z])('+TECH+')([^a-z]|$)','i'); var TECH_TAIL=new RegExp('\\s*[,;:\\-–—]\\s*('+TECH+')(?![a-z]).*$','i'); function cleanTitle(t,max){ max=max||300; var s=plain(t); /* служебная разметка Викиданных/Commons: «QS:P1476,de:"Jane Seymour "label QS:Lde,…» */ if(/\bQS:/.test(s)){ var qs=s.match(/QS:P1476,[\w-]+:"([^"]+)"/i)||s.match(/QS:L[\w-]+,"([^"]+)"/i),head=s.split(/\bQS:/)[0].replace(/[\s"]+$/,''); s=head||(qs?qs[1]:''); } s=s.replace(/\{\{\s*[\w-]+\s*\|\s*(?:1\s*=\s*)?([^{}|]*)\}\}/g,'$1').replace(/\{\{[^{}]*\}\}/g,' ').replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g,'$1').replace(/\b[PQ]\d{2,}\b/g,' '); s=s.replace(/^file:/i,'').replace(/\.(jpe?g|png|tiff?|gif|webp|svg)$/i,'').replace(/_/g,' '); s=s.replace(/https?:\/\/\S+/gi,' ').replace(/www\.\S+/gi,' '); s=s.split(/\s+[|\/]\s+/)[0]; /* скобки и квадратные скобки: оставляем только осмысленный текст, коды, годы и технику убираем */ s=s.replace(/\s*[\(\[][^()\[\]]*[\)\]]/g,function(m){ var inner=m.replace(/^\s*[\(\[]|[\)\]]\s*$/g,''); var letters=(inner.match(/\p{L}/gu)||[]).length; if(!letters||TECH_RE.test(inner)||inner.length>40||(/\d{3,}/.test(inner)&&letters<=8))return ''; return m; }); s=s.replace(/\s*[\(\[][^()\[\]]*$/,''); /* незакрытая скобка в конце */ s=s.replace(/^\s*(?:[A-Z]{1,5}[-.]?)+\d[\w.\-\/]*\s*[:.\-–—]\s+/,''); /* номер в начале: «SK-A-1234: …» */ s=s.replace(/\s*[-–—]\s*Google Art Project\s*\d*/ig,'').replace(/\s*[-–—]\s*Wikimedia Commons/ig,''); s=s.replace(TECH_TAIL,''); /* «…, oil on canvas» и подобное в конце */ s=s.replace(/\b(?:WGA|NG|SK-[A-Z]-|RP-[A-Z]-|inv\.?)\s*[\w.\-\/]*\d[\w.\-\/]*/gi,' '); s=s.replace(/[^\p{L}\p{N}\s.,;:!?'"«»“”‘’()\-–—&…]/gu,' '); /* служебные символы */ s=s.replace(/\(\s*\)/g,' ').replace(/([,;:!?\-–—])\1+/g,'$1').replace(/\s+/g,' '); s=s.replace(/^[\s.,;:\-–—]+|[\s,;:\-–—]+$/g,''); if(s.length>max){ var cut=s.slice(0,max),i=Math.max(cut.lastIndexOf(', '),cut.lastIndexOf('; '),cut.lastIndexOf(': '),cut.lastIndexOf(' - ')); if(i<40)i=cut.lastIndexOf(' '); s=cut.slice(0,i>0?i:max).replace(/[\s,;:\-–—]+$/,'')+'…'; } return s; } /* очистка автора, даты и прав: HTML, вики-разметка, ссылки, переносы строк, служебные символы; длинное обрезаем */ function cleanMeta(t,max){ max=max||120; var s=plain(t); s=s.replace(/\{\{[^{}]*\}\}/g,' ').replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g,'$1').replace(/\bQS:\S*/g,' ').replace(/\b[PQ]\d{2,}\b/g,' '); s=s.replace(/https?:\/\/\S+/gi,' ').replace(/www\.\S+/gi,' ').replace(/\S+@\S+\.\S+/g,' '); s=s.replace(/\s*\(\s*\)/g,' ').replace(/\s*\[\s*\]/g,' '); s=s.replace(/[^\p{L}\p{N}\s.,;:!?'"«»“”‘’()\-–—&\/…]/gu,' '); s=s.replace(/([,;:!?\-–—.])\1{1,}/g,function(m){return m[0]==='.'&&m.length===3?'…':m[0]}); s=s.replace(/\s+([,.;:!?])/g,'$1').replace(/\s+/g,' '); s=s.replace(/^[\s.,;:\-–—\/]+|[\s,;:\-–—\/]+$/g,''); if(s.length>max){var c=s.slice(0,max),i=Math.max(c.lastIndexOf(', '),c.lastIndexOf('; '),c.lastIndexOf(' '));s=c.slice(0,i>20?i:max).replace(/[\s,;:\-–—]+$/,'')+'…'} return s; } /* десктоп: название в одну строку, не поместилось: обрезается «…», полный текст в подсказке при наведении. мобильный: название в одну строку, не поместилось: плавно бегает туда-обратно */ function isDesk(){return !!(window.matchMedia&&window.matchMedia('(min-width:901px)').matches)} function fitTitle(){ var el=cap.querySelector('.at'),inn=el&&el.firstChild;if(!inn)return; el.classList.remove('run');inn.style.removeProperty('animation');el.removeAttribute('title'); if(isDesk()){ if(el.scrollWidth>el.clientWidth+1)el.setAttribute('title',el.textContent); 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='@media (min-width:901px){.art-cap .at{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}'+ '@media (max-width:900px){.art-cap .at{white-space:nowrap;overflow:hidden}.art-cap .at-in{display:inline-block}}'+ '@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); /* показ с плавной сменой: новый слайд проявляется поверх старого */ /* плитка #art-detail: случайный фрагмент текущей картины в натуральном размере (100%, один пиксель картины = один пиксель экрана) */ function updateDetail(im){ var d=document.getElementById('art-detail'); if(!d||!im||!im.naturalWidth)return; var cw=d.clientWidth,ch=d.clientHeight; if(!cw||!ch)return; /* плитка скрыта (мобильная версия) */ var nw=im.naturalWidth,nh=im.naturalHeight; function pos(n,c){return n>c?-rint(n-c+1):Math.round((c-n)/2)} var src=String(im.currentSrc||im.src).replace(/'/g,'%27'); var layer=document.createElement('div'); layer.className='art-detail-l'; layer.style.cssText="position:absolute;inset:0;opacity:0;background:url('"+src+"') no-repeat;background-size:"+nw+'px '+nh+'px;background-position:'+pos(nw,cw)+'px '+pos(nh,ch)+'px'; setTr(layer,'opacity '+FADE+'ms ease'); var old=d.querySelectorAll('.art-detail-l'); d.appendChild(layer); requestAnimationFrame(function(){requestAnimationFrame(function(){layer.style.opacity='1'})}); Array.prototype.forEach.call(old,function(o){setTr(o,'opacity '+FADE+'ms ease');o.style.opacity='0';setTimeout(function(){o.remove()},FADE+150)}); } (function(){var d=document.getElementById('art-detail');if(d){d.addEventListener('click',function(){updateDetail(curImg)});d.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();updateDetail(curImg)}})}})(); function show(a,im){ a.title=cleanTitle(a.title)||'Без названия'; a.artist=cleanMeta(a.artist,120);a.date=cleanMeta(a.date,40);a.museum=cleanMeta(a.museum,120);a.rights=cleanMeta(a.rights,80); curArt=a;paintLike(); 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();ringZoom(0,0);magRing(0,0);paintMag(); 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((isDesk()?a.title:cleanTitle(a.title,110))||'Без названия')+'
'+''+by+''+(by?' · ':'')+esc(a.museum)+'
'+ ''+esc(a.museum)+'
'; var rt=document.getElementById('art-rights'); if(rt)rt.textContent=(window.matchMedia&&window.matchMedia('(max-width:900px)').matches)?String(a.rights).replace('некоммерческое использование','некомм. использование'):a.rights; var pg=document.getElementById('art-page'); if(pg){pg.href=a.url;pg.textContent=a.linkText||'Страница в коллекции музея';pg.hidden=false} requestAnimationFrame(fitTitle); updateDetail(im); 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'); /* лупа включена по умолчанию */ /* ---------- круговой индикатор по контуру кнопки увеличения ---------- розовая дуга: сколько уже увеличено; белая вращающаяся дуга: загружается следующая картина */ var pend=0,zSvg=null,zRing=null; (function(){ if(!zoomBtn)return; var rs=document.createElement('style'); rs.textContent='.art-ctl .art-zoom{position:relative}'+ '.zring{position:absolute;left:-1px;top:-1px;width:40px;height:40px;overflow:visible;pointer-events:none}'+ '.zr-z{fill:none;stroke:#ff4f9b;stroke-width:2.4;stroke-dasharray:100 100;stroke-dashoffset:100}'+ '.zr-l{fill:none;stroke:rgba(255,255,255,.5);stroke-width:2.4;stroke-linecap:round;stroke-dasharray:26 74}'+ '.zr-lg{transform-origin:20px 20px;opacity:0;transition:opacity .2s}'+ '.zring.zr-load .zr-lg{opacity:1}.zring.zr-load .zr-z{opacity:.35}'+ '@keyframes zr-spin{to{transform:rotate(360deg)}}'+ '@keyframes zr-pulse{0%,100%{opacity:1}50%{opacity:.1}}'; document.head.appendChild(rs); zoomBtn.insertAdjacentHTML('beforeend',''); zSvg=zoomBtn.querySelector('.zring');zRing=zoomBtn.querySelector('.zr-z'); zoomBtn.querySelector('.zr-lg').style.setProperty('animation','zr-spin .9s linear infinite','important'); })(); /* f от 0 до 1; ms: за сколько плавно дойти (0 = сразу) */ function ringZoom(f,ms){ if(!zRing)return; f=Math.max(0,Math.min(1,f||0)); setTr(zRing,ms?'stroke-dashoffset '+ms+'ms linear':'none'); zRing.style.strokeDashoffset=String(100-f*100); } /* ---------- история: до 5 картин назад (в памяти хранятся данные и адрес картинки, браузер берёт её из кэша) ---------- */ var HIST_BACK=5,hist=[],hi=-1,prevBtn=document.getElementById('art-prev'); if(!prevBtn&&btn.parentNode){ prevBtn=document.createElement('button');prevBtn.type='button';prevBtn.id='art-prev';prevBtn.textContent='Предыдущая'; btn.parentNode.insertBefore(prevBtn,btn); } if(prevBtn)prevBtn.disabled=true; /* если следующая картина грузится дольше SLOW_MS, кнопка увеличения мягко мерцает до прозрачного, пока картина не загрузится */ var SLOW_MS=5000,slowT=0,slowOn=false; function pulse(on){ if(!zoomBtn)return; if(on)zoomBtn.style.setProperty('animation','zr-pulse 1.8s ease-in-out infinite','important'); else zoomBtn.style.removeProperty('animation'); } function updLoad(){ if(zSvg)zSvg.classList.toggle('zr-load',!!busy&&!shown); if(prevBtn)prevBtn.disabled=!!busy||hi<=0; if(pend>0){ if(!slowT&&!slowOn)slowT=setTimeout(function(){slowT=0;slowOn=true;pulse(true)},SLOW_MS); }else{ clearTimeout(slowT);slowT=0; if(slowOn){slowOn=false;pulse(false)} } } /* показать картину из истории по номеру */ function goHist(i){ var h=hist[i];if(!h||busy)return; clearTimeout(timer);busy=true;btn.disabled=true;updLoad(); loadImg(h.src).then(function(im){ show(h.a,im);hi=i;busy=false;enableNext();updLoad();schedule(); },function(){ busy=false;enableNext();updLoad();schedule(); }); } function back(){if(!busy&&hi>0)goHist(hi-1)} if(prevBtn)prevBtn.addEventListener('click',back); function startZoom(im){if(!im)return;setTr(im,'transform '+DELAY+'ms linear');im.style.transform='scale('+ZOOM+')';ringZoom(1,DELAY);magRing((ZOOM-1)/(ZMAX-1),DELAY)} var zoomT=0; function armZoom(im){clearTimeout(zoomT);zoomT=setTimeout(function(){if(zoomOn&&im===curImg)startZoom(im)},DELAY)} function stopZoom(im){clearTimeout(zoomT);ringZoom(0,600);magRing(0,600);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':''; ringZoom((view.s-1)/(ZOOM-1),anim?300:0); paintMag(); magRing((view.s-1)/(ZMAX-1),anim?300:0); } function clampView(im){ /* реальный размер изображения внутри блока (если оно вписано через object-fit:contain, блок больше самой картины) */ var sl=im.parentNode,rw=im.offsetWidth,rh=im.offsetHeight; if(im.naturalWidth&&im.naturalHeight){var r=Math.min(rw/im.naturalWidth,rh/im.naturalHeight);rw=im.naturalWidth*r;rh=im.naturalHeight*r} var mx=Math.max(0,(rw*view.s-sl.clientWidth)/2),my=Math.max(0,(rh*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); clearTimeout(holdWd);holdWd=setTimeout(chkHold,6000); } var ptrs={},ptrN=0,holdWd=0; function release(){ if(!hold)return; hold=false;clearTimeout(holdWd);schedule(EXTRA); } /* страховка: если событие «палец отпущен» потерялось, автосмена всё равно вернётся */ function chkHold(){if(!hold)return;if(!drag&&!ptrN)release();else holdWd=setTimeout(chkHold,3000)} frame.addEventListener('pointerdown',function(e){ if(e.pointerType==='mouse'&&e.button!==0)return; if(!ptrs[e.pointerId]){ptrs[e.pointerId]=1;ptrN++} if(!curImg||drag)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 endPtr(e){ if(!ptrs[e.pointerId])return; delete ptrs[e.pointerId];ptrN=Math.max(0,ptrN-1); if(drag&&drag.id===e.pointerId){drag=null;frame.classList.remove('art-grab')} if(!ptrN&&hold)release(); } frame.addEventListener('pointerup',endPtr); frame.addEventListener('pointercancel',endPtr); frame.addEventListener('lostpointercapture',endPtr); window.addEventListener('pointerup',endPtr); window.addEventListener('pointercancel',endPtr); window.addEventListener('blur',function(){ptrs={};ptrN=0;drag=null;frame.classList.remove('art-grab');if(hold)release()}); 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); /* ---------- кнопка-лупа в правом нижнем углу плитки (десктоп, при наведении на картину) ---------- каждый клик увеличивает картину ещё на ZSTEP от исходного размера, до ZMAX (+500%); после максимума клик возвращает исходный размер */ /* до ZFROM шаг ZSTEP (10%), дальше крупный шаг ZSTEP2 (+100%): 300%, 400%, 500%, 600% от исходного */ var ZSTEP=0.1,ZFROM=3,ZSTEP2=1,ZMAX=6,ZCROSS=5,ctrlDown=false,zoomDown=false,magBtn=null,magIco=null,magR=null,magStar=null; var ICO_PLUS=''; var ICO_MINUS=''; (function(){ var ms=document.createElement('style'); ms.textContent='.art-mag{display:none}'+ '@media (min-width:901px) and (hover:hover){'+ '.art-mag{display:inline-flex;position:absolute;right:14px;bottom:14px;z-index:5;box-sizing:border-box;width:40px;height:40px;padding:0;align-items:center;justify-content:center;font:inherit;color:var(--ink);background:color-mix(in srgb,var(--surface) 88%,transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:50%;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .2s,border-color .15s,color .15s}'+ '.art-frame:hover .art-mag,.art-mag:focus-visible{opacity:1;pointer-events:auto}'+ '.art-mag:hover{border-color:var(--accent);color:var(--accent)}}'+ '.art-mag-i{display:inline-flex;line-height:0}'+ '.art-star{position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;z-index:4;pointer-events:none;opacity:0;transition:opacity .3s}.art-star.on{opacity:1}'+ '.art-mag-ring{position:absolute;left:-1px;top:-1px;width:40px;height:40px;overflow:visible;pointer-events:none}'+ '.art-mag-r{fill:none;stroke:var(--accent);stroke-width:1.5;stroke-dasharray:100 100;stroke-dashoffset:100}'; document.head.appendChild(ms); magBtn=document.createElement('button');magBtn.type='button';magBtn.className='art-mag'; magBtn.innerHTML=''; magIco=magBtn.querySelector('.art-mag-i');magR=magBtn.querySelector('.art-mag-r'); frame.appendChild(magBtn); /* крестик в центре плитки: появляется при увеличении от ZCROSS (+400%) */ magStar=document.createElement('div');magStar.className='art-star';magStar.setAttribute('aria-hidden','true'); magStar.innerHTML=''; frame.appendChild(magStar); paintMag(); /* клики по кнопке не должны запускать перетаскивание и двойной клик рамки */ magBtn.addEventListener('pointerdown',function(e){e.stopPropagation()}); magBtn.addEventListener('dblclick',function(e){e.stopPropagation()}); magBtn.addEventListener('click',function(){ var im=curImg;if(!im)return; takeOver(im); var old=view.s,down=ctrlDown||zoomDown||old>=ZMAX-0.001; /* на исходном размере уменьшать некуда: без Ctrl направление снова становится «увеличение» */ if(down&&old<=1.001&&!ctrlDown){down=false;zoomDown=false} if(down){ /* уменьшение: до +200% шаг 10%, выше шаг 100% */ if(old>1.001){ var d=old>ZFROM+0.001?Math.max(ZFROM,Math.round((old-ZSTEP2)*100)/100):Math.max(1,Math.round((old-ZSTEP)*100)/100),kd=d/old; view.s=d;view.x*=kd;view.y*=kd; if(d<=1.001){view={s:1,x:0,y:0};zoomDown=false}else{zoomDown=!ctrlDown||zoomDown;clampView(im)} } }else{ var n=old