add lightbox and open image in new tab (closes #5)

This commit is contained in:
Luca Orio 2022-12-22 23:37:19 -05:00
parent 7bba5b139b
commit 0f8b47e65f
13 changed files with 2731 additions and 336 deletions

View file

1698
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,7 @@
"@typescript-eslint/parser": "^5.2.0",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"lightgallery": "^2.7.0",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "^4.4.4"

38
src/build-horizontal.ts Normal file
View file

@ -0,0 +1,38 @@
const buildHorizontal = (
container: HTMLElement,
imagesList: {[key: string]: any},
settings: {[key: string]: any}
) => {
// inject the gallery wrapper
const gallery = container.createEl('div')
gallery.addClass('grid-wrapper')
gallery.style.display = 'flex'
gallery.style.flexWrap = 'wrap'
gallery.style.marginRight = `-${settings.gutter}px`
// inject and style images
imagesList.forEach((file: {[key: string]: string}) => {
const figure = gallery.createEl('figure')
figure.addClass('grid-item')
figure.style.margin = `0px ${settings.gutter}px ${settings.gutter}px 0px`
figure.style.width = 'auto'
figure.style.height = `${settings.height}px`
figure.style.borderRadius = `${settings.radius}px`
figure.style.flex = '1 0 auto'
figure.style.overflow = 'hidden'
figure.setAttribute('data-name', file.name)
figure.setAttribute('data-folder', file.name)
figure.setAttribute('data-src', file.uri)
const img = figure.createEl('img')
img.style.objectFit = 'cover'
img.style.width = '100%'
img.style.height = '100%'
img.style.borderRadius = '0px'
img.src = file.uri
})
return gallery
}
export default buildHorizontal

47
src/build-lightbox.ts Normal file
View file

@ -0,0 +1,47 @@
import { App, Platform } from 'obsidian'
import Lightbox from 'lightgallery';
import LightboxThumbs from 'lightgallery/plugins/thumbnail'
const lightbox = (gallery: HTMLElement, imagesList: {[key: string]: any}, app: App) => {
// attach a custom button to open original image, only on desktop
if (Platform.isDesktop) globalSearchBtn(gallery, imagesList)
// setup a lightbox for the gallery
const galleryLightbox = Lightbox(gallery, {
plugins: [LightboxThumbs],
counter: false,
download: false,
thumbnail: true,
loop: false,
mode: 'lg-fade',
// todo: replace with GPLv3 compatible license whenever available
licenseKey: '1234-1234-123-1234',
})
// if we are on mobile, make sure to remove unnecessary controls
if (Platform.isIosApp || Platform.isAndroidApp) {
const elements:NodeListOf<HTMLElement> = document.querySelectorAll('.lg-close, .lg-prev, .lg-next')
for (const element of elements) {
element.style.display = 'none'
}
}
return galleryLightbox
}
const globalSearchBtn = (gallery: HTMLElement, imagesList: {[key: string]: any}) => {
gallery.addEventListener('lgInit', (event: CustomEvent) => {
const galleryInstance = event.detail.instance
const btn ='<button type="button" id="btn-glob-search" class="lg-icon btn-glob-search"></button>'
galleryInstance.outer.find('.lg-toolbar').append(btn)
galleryInstance.outer.find('#btn-glob-search').on('click', () => {
const index = galleryInstance.index
const selected = imagesList[index]
app.workspace.openLinkText(selected.name, selected.folder, true, {active: true})
galleryInstance.closeGallery()
});
})
}
export default lightbox

32
src/build-vertical.ts Normal file
View file

@ -0,0 +1,32 @@
const buildVertical = (
container: HTMLElement,
imagesList: {[key: string]: any},
settings: {[key: string]: any}
) => {
// inject the gallery wrapper
const gallery = container.createEl('div')
gallery.addClass('grid-wrapper')
gallery.style.lineHeight = '0px'
gallery.style.columnCount = `${settings.columns}`
gallery.style.columnGap = `${settings.gutter}px`
// inject and style images
imagesList.forEach((file: {[key: string]: string}) => {
const figure = gallery.createEl('div')
figure.addClass('grid-item')
figure.style.marginBottom = `${settings.gutter}px`
figure.style.width = '100%'
figure.style.height = 'auto'
figure.setAttribute('data-name', file.name)
figure.setAttribute('data-folder', file.name)
figure.setAttribute('data-src', file.uri)
const img = figure.createEl('img')
img.style.borderRadius = `${settings.radius}px`
img.src = file.uri
})
return gallery
}
export default buildVertical

46
src/get-imgs-list.ts Normal file
View file

@ -0,0 +1,46 @@
import { App, TFolder, TFile } from 'obsidian'
import renderError from './render-error'
const getImagesList = (
app: App,
container: HTMLElement,
settings: {[key: string]: any}
) => {
// retrieve a list of the files
const folder = app.vault.getAbstractFileByPath(settings.path)
let files
if (folder instanceof TFolder) { files = folder.children }
else {
const error = 'The folder doesn\'t exist, or it\'s empty!'
renderError(container, error)
throw new Error(error)
}
// filter the list of files to make sure we're dealing with images only
const validExtensions = ["jpeg", "jpg", "gif", "png", "webp", "tiff", "tif"]
const images = files.filter(file => {
if (file instanceof TFile && validExtensions.includes(file.extension)) return file
})
// sort the list by name, mtime, or ctime
const orderedImages = images.sort((a: any, b: any) => {
const refA = settings.sortby === 'name' ? a['name'].toUpperCase() : a.stat[settings.sortby]
const refB = settings.sortby === 'name' ? b['name'].toUpperCase() : b.stat[settings.sortby]
return (refA < refB) ? -1 : (refA > refB) ? 1 : 0
})
// re-sort again by ascending or descending order
const sortedImages = settings.sort === 'asc' ? orderedImages : orderedImages.reverse()
// return an array of objects
return sortedImages.map(file => {
return {
name: file.name,
folder: file.parent.path,
uri: app.vault.adapter.getResourcePath(file.path)
}
})
}
export default getImagesList

52
src/get-settings.ts Normal file
View file

@ -0,0 +1,52 @@
import { normalizePath, parseYaml, Platform } from 'obsidian'
import renderError from './render-error'
const getSettings = (src: string, container: HTMLElement) => {
// parse the settings from the code block
const settingsSrc: any = parseYaml(src)
// check for required settings
if (settingsSrc === undefined) {
const error = 'Cannot parse YAML!'
renderError(container, error)
throw new Error(error)
}
if (!settingsSrc.path) {
const error = 'Please specify a path!'
renderError(container, error)
throw new Error(error)
}
// store settings, normalize and set sensible defaults
const settings = {
path: undefined as string,
type: undefined as string,
radius: undefined as number,
gutter: undefined as string,
sortby: undefined as string,
sort: undefined as string,
mobile: undefined as number,
columns: undefined as number,
height: undefined as number,
}
settings.path = normalizePath(settingsSrc.path)
settings.type = settingsSrc.type ?? 'horizontal'
settings.radius = settingsSrc.radius ?? 0
settings.gutter = settingsSrc.gutter ?? 8
settings.sortby = settingsSrc.sortby ?? 'ctime'
settings.sort = settingsSrc.sort ?? 'desc'
// settings for vertical mansory only
settings.mobile = settingsSrc.mobile ?? 1
if (Platform.isDesktop) settings.columns = settingsSrc.columns ?? 3
else settings.columns = settings.mobile
// settings for horizontal mansory only
settings.height = settingsSrc.height ?? 260
return settings
}
export default getSettings

View file

@ -1,162 +0,0 @@
import { App, MarkdownRenderChild, TFolder, TFile, Platform, normalizePath, parseYaml } from 'obsidian'
import ImgGallery from './main'
export class imgGalleryRenderer extends MarkdownRenderChild {
private _gallery: HTMLElement = null
private _settings: {[key: string]: any} = {}
private _imagesList: string[] = []
constructor(
public plugin: ImgGallery,
public src: string,
public container: HTMLElement,
public app: App
) {
super(container)
}
async onload() {
this._getSettings()
this._getImagesList()
if (this._settings.type === 'horizontal') this._injectHorMasonry()
else this._injectVerMasonry()
}
async onunload() {
if (this._gallery) {
this._gallery.remove()
this._gallery = null
}
}
private _getSettings() {
// parse the settings from the code block
const settingsObj: any = parseYaml(this.src)
// check for required settings
if (settingsObj === undefined) {
const error = 'Cannot parse YAML!'
this._renderError(error)
throw new Error(error)
}
if (!settingsObj.path) {
const error = 'Please specify a path!'
this._renderError(error)
throw new Error(error)
}
// store settings, normalize and set sensible defaults
this._settings.path = normalizePath(settingsObj.path)
this._settings.type = settingsObj.type ?? 'horizontal'
this._settings.radius = settingsObj.radius ?? 0
this._settings.gutter = settingsObj.gutter ?? 8
this._settings.sortby = settingsObj.sortby ?? 'ctime'
this._settings.sort = settingsObj.sort ?? 'desc'
// settings for vertical mansory only
this._settings.mobile = settingsObj.mobile ?? 1
if (Platform.isDesktop) this._settings.columns = settingsObj.columns ?? 3
else this._settings.columns = this._settings.mobile
// settings for horizontal mansory only
this._settings.height = settingsObj.height ?? 260
}
private _getImagesList() {
// retrieve a list of the files
const folder = this.app.vault.getAbstractFileByPath(this._settings.path)
let files
if (folder instanceof TFolder) { files = folder.children }
else {
const error = 'The folder doesn\'t exist, or it\'s empty!'
this._renderError(error)
throw new Error(error)
}
// filter the list of files to make sure we're dealing with images only
const validExtensions = ["jpeg", "jpg", "gif", "png", "webp", "tiff", "tif"]
const images = files.filter(file => {
if (file instanceof TFile && validExtensions.includes(file.extension)) return file
})
// sort the list by name, mtime, or ctime
const orderedImages = images.sort((a: any, b: any) => {
const refA = this._settings.sortby === 'name' ? a['name'].toUpperCase() : a.stat[this._settings.sortby]
const refB = this._settings.sortby === 'name' ? b['name'].toUpperCase() : b.stat[this._settings.sortby]
return (refA < refB) ? -1 : (refA > refB) ? 1 : 0
})
// re-sort again by ascending or descending order
const sortedImages = this._settings.sort === 'asc' ? orderedImages : orderedImages.reverse()
// return an array of strings only
this._imagesList = sortedImages.map(file =>
this.app.vault.adapter.getResourcePath(file.path)
)
}
private _injectVerMasonry() {
// inject the gallery wrapper
const gallery = this.container.createEl('div')
gallery.addClass('grid-wrapper')
gallery.style.lineHeight = '0px'
gallery.style.columnCount = `${this._settings.columns}`
gallery.style.columnGap = `${this._settings.gutter}px`
this._gallery = gallery
// inject and style images
this._imagesList.forEach((uri) => {
const img = this._gallery.createEl('img')
img.addClass('grid-item')
img.style.marginBottom = `${this._settings.gutter}px`
img.style.width = '100%'
img.style.height = 'auto'
img.style.borderRadius = `${this._settings.radius}px`
img.src = uri
})
}
private _injectHorMasonry() {
// inject the gallery wrapper
const gallery = this.container.createEl('div')
gallery.addClass('grid-wrapper')
gallery.style.display = 'flex'
gallery.style.flexWrap = 'wrap'
gallery.style.marginRight = `-${this._settings.gutter}px`
this._gallery = gallery
// inject and style images
this._imagesList.forEach((uri) => {
const figure = this._gallery.createEl('figure')
figure.addClass('grid-item')
figure.style.margin = `0px ${this._settings.gutter}px ${this._settings.gutter}px 0px`
figure.style.height = `${this._settings.height}px`
figure.style.borderRadius = `${this._settings.radius}px`
figure.style.flex = '1 0 auto'
figure.style.overflow = 'hidden'
const img = figure.createEl('img')
img.style.objectFit = 'cover'
img.style.width = '100%'
img.style.height = '100%'
img.style.borderRadius = '0px'
img.src = uri
})
}
private _renderError(error: string) {
// render a custom error and style it
const wrapper = this.container.createEl('div')
const content = wrapper.createEl('p', {text: `(Error) Image Gallery: ${error}`});
wrapper.style.borderRadius = '4px'
wrapper.style.padding = '2px 16px'
wrapper.style.backgroundColor = '#e50914'
wrapper.style.color = '#fff'
wrapper.style.fontWeight = 'bolder'
}
}

56
src/init.ts Normal file
View file

@ -0,0 +1,56 @@
import { App, MarkdownRenderChild } from 'obsidian'
import ImgGallery from './main'
import getImagesList from './get-imgs-list'
import getSettings from './get-settings'
import buildHorizontal from './build-horizontal'
import buildVertical from './build-vertical'
import buildLightbox from './build-lightbox'
export class imgGalleryInit extends MarkdownRenderChild {
private _gallery: HTMLElement = null
private _lightbox: any = null
private _settings: {[key: string]: any} = {}
private _imagesList: {[key: string]: any} = {}
constructor(
public plugin: ImgGallery,
public src: string,
public container: HTMLElement,
public app: App
) {
super(container)
}
async onload() {
// parse and normalize settings
this._settings = getSettings(this.src, this.container)
this._imagesList = getImagesList(this.app, this.container, this._settings)
// inject the pertinent kind of gallery
if (this._settings.type === 'horizontal') {
this._gallery = buildHorizontal(this.container, this._imagesList, this._settings)
} else if (this._settings.type === 'vertical') {
this._gallery = buildVertical(this.container, this._imagesList, this._settings)
}
// initialize a lightbox
this._lightbox = buildLightbox(this._gallery, this._imagesList, this.app)
}
async onunload() {
// todo: monitor the bug attached below (obsidian 1.1.8)
// https://forum.obsidian.md/t/markdown-render-childes-are-not-being-unloaded-when-switching-notes-in-the-same-tab/49681
// destroy the gallery
if (this._gallery) {
this._gallery.remove()
this._gallery = null
}
// destroy the lightbox
if (this._lightbox) {
this._lightbox.destroy()
this._lightbox = null
}
}
}

View file

@ -1,10 +1,10 @@
import { Plugin } from 'obsidian'
import { imgGalleryRenderer } from './img-gallery-renderer';
import { imgGalleryInit } from './init';
export default class ImgGallery extends Plugin {
async onload() {
this.registerMarkdownCodeBlockProcessor('img-gallery', (src, el, ctx) => {
const handler = new imgGalleryRenderer(this, src, el, this.app)
const handler = new imgGalleryInit(this, src, el, this.app)
ctx.addChild(handler)
})
}

13
src/render-error.ts Normal file
View file

@ -0,0 +1,13 @@
const renderError = (container: HTMLElement, error: string) => {
// render a custom error and style it
const wrapper = container.createEl('div')
wrapper.createEl('p', {text: `(Error) Image Gallery: ${error}`});
wrapper.style.borderRadius = '4px'
wrapper.style.padding = '2px 16px'
wrapper.style.backgroundColor = '#e50914'
wrapper.style.color = '#fff'
wrapper.style.fontWeight = 'bolder'
}
export default renderError

896
styles.css Normal file
View file

@ -0,0 +1,896 @@
/* ========================================================================= */
/* custom colors and icons for dark theme */
/* ========================================================================= */
body.theme-dark .lg-backdrop,
body.theme-dark .lg-outer .lg-thumb-outer,
body.theme-dark .lg-next,
body.theme-dark .lg-prev {
background-color: rgba(0, 0, 0, 1) !important;
}
body.theme-dark .lg-outer .lg-thumb-item.active,
body.theme-dark .lg-outer .lg-thumb-item:hover {
outline: 2px solid rgba(255, 255, 255, 1);
}
body.theme-dark .lg-next {
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='22' height='22' viewBox='0 0 16 16'%3E%3Cpath fill='%23FFFFFF' d='M9.707 13.707l5-5c0.391-0.39 0.391-1.024 0-1.414l-5-5c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l3.293 3.293h-9.586c-0.552 0-1 0.448-1 1s0.448 1 1 1h9.586l-3.293 3.293c-0.195 0.195-0.293 0.451-0.293 0.707s0.098 0.512 0.293 0.707c0.391 0.391 1.024 0.391 1.414 0z'%3E%3C/path%3E%3C/svg%3E%0A");
}
body.theme-dark .lg-prev {
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='22' height='22' viewBox='0 0 16 16'%3E%3Cpath fill='%23FFFFFF' d='M6.293 13.707l-5-5c-0.391-0.39-0.391-1.024 0-1.414l5-5c0.391-0.391 1.024-0.391 1.414 0s0.391 1.024 0 1.414l-3.293 3.293h9.586c0.552 0 1 0.448 1 1s-0.448 1-1 1h-9.586l3.293 3.293c0.195 0.195 0.293 0.451 0.293 0.707s-0.098 0.512-0.293 0.707c-0.391 0.391-1.024 0.391-1.414 0z'%3E%3C/path%3E%3C/svg%3E%0A");
}
body.theme-dark .lg-close {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
}
body.theme-dark .btn-glob-search {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z'%3E%3C/path%3E%3Cpolyline points='14 2 14 8 20 8'%3E%3C/polyline%3E%3Ccircle cx='11.5' cy='14.5' r='2.5'%3E%3C/circle%3E%3Cpath d='M13.25 16.25 15 18'%3E%3C/path%3E%3C/svg%3E");
}
/* ========================================================================= */
/* custom colors and icons for light theme */
/* ========================================================================= */
body.theme-light .lg-backdrop,
body.theme-light .lg-outer .lg-thumb-outer,
body.theme-light .lg-next,
body.theme-light .lg-prev {
background-color: rgba(255, 255, 255, 1);
}
body.theme-light .lg-outer .lg-thumb-item.active,
body.theme-light .lg-outer .lg-thumb-item:hover {
outline: 2px solid rgba(0, 0, 0, 1);
}
body.theme-light .lg-next {
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='22' height='22' viewBox='0 0 16 16'%3E%3Cpath fill='%23000000' d='M9.707 13.707l5-5c0.391-0.39 0.391-1.024 0-1.414l-5-5c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l3.293 3.293h-9.586c-0.552 0-1 0.448-1 1s0.448 1 1 1h9.586l-3.293 3.293c-0.195 0.195-0.293 0.451-0.293 0.707s0.098 0.512 0.293 0.707c0.391 0.391 1.024 0.391 1.414 0z'%3E%3C/path%3E%3C/svg%3E%0A");
}
body.theme-light .lg-prev {
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='22' height='22' viewBox='0 0 16 16'%3E%3Cpath fill='%23000000' d='M6.293 13.707l-5-5c-0.391-0.39-0.391-1.024 0-1.414l5-5c0.391-0.391 1.024-0.391 1.414 0s0.391 1.024 0 1.414l-3.293 3.293h9.586c0.552 0 1 0.448 1 1s-0.448 1-1 1h-9.586l3.293 3.293c0.195 0.195 0.293 0.451 0.293 0.707s-0.098 0.512-0.293 0.707c-0.391 0.391-1.024 0.391-1.414 0z'%3E%3C/path%3E%3C/svg%3E%0A");
}
body.theme-light .lg-close {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
}
body.theme-light .btn-glob-search {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z'%3E%3C/path%3E%3Cpolyline points='14 2 14 8 20 8'%3E%3C/polyline%3E%3Ccircle cx='11.5' cy='14.5' r='2.5'%3E%3C/circle%3E%3Cpath d='M13.25 16.25 15 18'%3E%3C/path%3E%3C/svg%3E");
}
/* ========================================================================= */
/* customized styles for lightgallery css */
/* ========================================================================= */
.lg-container {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
.lg-container button:not(.clickable-icon) {
box-shadow: none !important;
}
.lg-next,
.lg-prev {
border-radius: 2px;
cursor: pointer;
display: block;
margin-top: -10px;
padding: 22px;
width: 22px;
height: 22px;
position: absolute;
top: 50%;
z-index: 1084;
outline: none;
border: none;
opacity: 0.6 !important;
}
.lg-next.disabled,
.lg-prev.disabled {
opacity: 0 !important;
cursor: default;
}
.lg-next:hover:not(.disabled),
.lg-prev:hover:not(.disabled) {
opacity: 1 !important;
}
.lg-single-item .lg-next, .lg-single-item
.lg-prev {
display: none;
}
.lg-next {
right: 20px;
background-position: center;
background-repeat: no-repeat;
opacity: 0.6;
}
.lg-next:before {
content: '';
}
.lg-prev {
left: 20px;
background-position: center;
background-repeat: no-repeat;
opacity: 0.6;
}
.lg-prev:after {
content: '';
}
@-webkit-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-moz-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-ms-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-webkit-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@-moz-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@-ms-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
.lg-outer.lg-right-end .lg-object {
-webkit-animation: lg-right-end 0.3s;
-o-animation: lg-right-end 0.3s;
animation: lg-right-end 0.3s;
position: relative;
}
.lg-outer.lg-left-end .lg-object {
-webkit-animation: lg-left-end 0.3s;
-o-animation: lg-left-end 0.3s;
animation: lg-left-end 0.3s;
position: relative;
}
.lg-toolbar {
z-index: 1082;
left: 0;
position: absolute;
top: 0;
width: 100%;
padding: 0px 20px;
}
.lg-media-overlap .lg-toolbar {
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4));
}
.lg-toolbar .lg-icon {
cursor: pointer;
float: right;
width: 22px;
height: 22px;
padding: 22px;
outline: medium none;
will-change: opacity;
-webkit-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
background: none;
border: none;
box-shadow: none;
opacity: 0.6;
}
.lg-toolbar .lg-icon:hover {
opacity: 1;
}
.lg-toolbar .lg-close {
background-position: center;
background-repeat: no-repeat;
opacity: 0.6;
}
.lg-toolbar .lg-close:after {
content: '';
}
.lg-sub-html {
color: #eee;
font-size: 16px;
padding: 10px 40px;
text-align: center;
z-index: 1080;
opacity: 0;
-webkit-transition: opacity 0.2s ease-out 0s;
-o-transition: opacity 0.2s ease-out 0s;
transition: opacity 0.2s ease-out 0s;
}
.lg-sub-html h4 {
margin: 0;
font-size: 13px;
font-weight: bold;
}
.lg-sub-html p {
font-size: 12px;
margin: 5px 0 0;
}
.lg-sub-html a {
color: inherit;
}
.lg-sub-html a:hover {
text-decoration: underline;
}
.lg-media-overlap .lg-sub-html {
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.6));
}
.lg-item .lg-sub-html {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
.lg-error-msg {
font-size: 14px;
color: #999;
}
.lg-counter {
color: #999;
display: inline-block;
font-size: 16px;
padding-left: 20px;
padding-top: 12px;
height: 47px;
vertical-align: middle;
}
.lg-closing .lg-toolbar,
.lg-closing .lg-prev,
.lg-closing .lg-next,
.lg-closing .lg-sub-html {
opacity: 0;
-webkit-transition: -webkit-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
-moz-transition: -moz-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
-o-transition: -o-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
transition: transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
}
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item:not(.lg-zoomable) .lg-img-wrap,
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item:not(.lg-zoomable) .lg-video-cont {
opacity: 0;
-moz-transform: scale3d(0.5, 0.5, 0.5);
-o-transform: scale3d(0.5, 0.5, 0.5);
-ms-transform: scale3d(0.5, 0.5, 0.5);
-webkit-transform: scale3d(0.5, 0.5, 0.5);
transform: scale3d(0.5, 0.5, 0.5);
will-change: transform, opacity;
-webkit-transition: -webkit-transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
-moz-transition: -moz-transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
-o-transition: -o-transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
transition: transform 250ms cubic-bezier(0, 0, 0.25, 1) 0s, opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
}
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item:not(.lg-zoomable).lg-complete .lg-img-wrap,
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item:not(.lg-zoomable).lg-complete .lg-video-cont {
opacity: 1;
-moz-transform: scale3d(1, 1, 1);
-o-transform: scale3d(1, 1, 1);
-ms-transform: scale3d(1, 1, 1);
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
.lg-icon:focus-visible {
color: #fff;
border-radius: 3px;
outline: 1px dashed rgba(255, 255, 255, 0.6);
}
.lg-toolbar .lg-icon:focus-visible {
border-radius: 8px;
outline-offset: -5px;
}
.lg-group:after {
content: '';
display: table;
clear: both;
}
.lg-container {
display: none;
outline: none;
}
.lg-container.lg-show {
display: block;
}
.lg-on {
scroll-behavior: unset;
}
.lg-overlay-open {
overflow: hidden;
}
.lg-toolbar,
.lg-prev,
.lg-next,
.lg-pager-outer,
.lg-hide-sub-html .lg-sub-html {
opacity: 0;
will-change: transform, opacity;
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
-moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
-o-transition: -o-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
}
.lg-show-in .lg-toolbar,
.lg-show-in .lg-prev,
.lg-show-in .lg-next,
.lg-show-in .lg-pager-outer {
opacity: 1;
}
.lg-show-in.lg-hide-sub-html .lg-sub-html {
opacity: 1;
}
.lg-show-in .lg-hide-items .lg-prev {
opacity: 0;
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
.lg-show-in .lg-hide-items .lg-next {
opacity: 0;
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
.lg-show-in .lg-hide-items .lg-toolbar {
opacity: 0;
-webkit-transform: translate3d(0, -10px, 0);
transform: translate3d(0, -10px, 0);
}
.lg-show-in .lg-hide-items.lg-hide-sub-html .lg-sub-html {
opacity: 0;
-webkit-transform: translate3d(0, 20px, 0);
transform: translate3d(0, 20px, 0);
}
.lg-outer {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1050;
text-align: left;
opacity: 0.001;
outline: none;
will-change: auto;
overflow: hidden;
-webkit-transition: opacity 0.15s ease 0s;
-o-transition: opacity 0.15s ease 0s;
transition: opacity 0.15s ease 0s;
}
.lg-outer * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.lg-outer.lg-zoom-from-image {
opacity: 1;
}
.lg-outer.lg-visible {
opacity: 1;
}
.lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-prev-slide, .lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-next-slide, .lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-current {
-webkit-transition-duration: inherit !important;
transition-duration: inherit !important;
-webkit-transition-timing-function: inherit !important;
transition-timing-function: inherit !important;
}
.lg-outer.lg-css3.lg-dragging .lg-item.lg-prev-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-next-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-current {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
opacity: 1;
}
.lg-outer.lg-grab img.lg-object {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: -o-grab;
cursor: -ms-grab;
cursor: grab;
}
.lg-outer.lg-grabbing img.lg-object {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: -o-grabbing;
cursor: -ms-grabbing;
cursor: grabbing;
}
.lg-outer .lg-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.lg-outer .lg-inner {
width: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
-webkit-transition: opacity 0s;
-o-transition: opacity 0s;
transition: opacity 0s;
white-space: nowrap;
}
.lg-outer .lg-item {
display: none !important;
}
.lg-outer.lg-css3 .lg-prev-slide,
.lg-outer.lg-css3 .lg-current,
.lg-outer.lg-css3 .lg-next-slide {
display: inline-block !important;
}
.lg-outer.lg-css .lg-current {
display: inline-block !important;
}
.lg-outer .lg-item,
.lg-outer .lg-img-wrap {
display: inline-block;
text-align: center;
position: absolute;
width: 100%;
height: 100%;
}
.lg-outer .lg-item:before,
.lg-outer .lg-img-wrap:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.lg-outer .lg-img-wrap {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
white-space: nowrap;
font-size: 0;
}
.lg-outer .lg-item.lg-complete {
background-image: none;
}
.lg-outer .lg-item.lg-current {
z-index: 1060;
}
.lg-outer .lg-object {
display: inline-block;
vertical-align: middle;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
position: relative;
}
.lg-outer .lg-empty-html.lg-sub-html,
.lg-outer .lg-empty-html .lg-sub-html {
display: none;
}
.lg-outer.lg-hide-download .lg-download {
opacity: 0.75;
pointer-events: none;
}
.lg-outer .lg-first-slide .lg-dummy-img {
position: absolute;
top: 50%;
left: 50%;
}
.lg-outer.lg-components-open:not(.lg-zoomed) .lg-components {
-webkit-transform: translate3d(0, 0%, 0);
transform: translate3d(0, 0%, 0);
opacity: 1;
}
.lg-outer.lg-components-open:not(.lg-zoomed) .lg-sub-html {
opacity: 1;
transition: opacity 0.2s ease-out 0.15s;
}
.lg-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1040;
opacity: 0;
will-change: auto;
-webkit-transition: opacity 333ms ease-in 0s;
-o-transition: opacity 333ms ease-in 0s;
transition: opacity 333ms ease-in 0s;
}
.lg-backdrop.in {
opacity: 1;
}
.lg-css3.lg-no-trans .lg-prev-slide,
.lg-css3.lg-no-trans .lg-next-slide,
.lg-css3.lg-no-trans .lg-current {
-webkit-transition: none 0s ease 0s !important;
-moz-transition: none 0s ease 0s !important;
-o-transition: none 0s ease 0s !important;
transition: none 0s ease 0s !important;
}
.lg-css3.lg-use-css3 .lg-item {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}
.lg-css3.lg-fade .lg-item {
opacity: 0;
}
.lg-css3.lg-fade .lg-item.lg-current {
opacity: 1;
}
.lg-css3.lg-fade .lg-item.lg-prev-slide, .lg-css3.lg-fade .lg-item.lg-next-slide, .lg-css3.lg-fade .lg-item.lg-current {
-webkit-transition: opacity 0.1s ease 0s;
-moz-transition: opacity 0.1s ease 0s;
-o-transition: opacity 0.1s ease 0s;
transition: opacity 0.1s ease 0s;
}
.lg-css3.lg-use-css3 .lg-item.lg-start-progress {
-webkit-transition: -webkit-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
-o-transition: -o-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
transition: transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
}
.lg-css3.lg-use-css3 .lg-item.lg-start-end-progress {
-webkit-transition: -webkit-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
-o-transition: -o-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item {
opacity: 0;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
opacity: 1;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current {
-webkit-transition: -webkit-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
-o-transition: -o-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
}
.lg-container {
display: none;
}
.lg-container.lg-show {
display: block;
}
.lg-container.lg-dragging-vertical .lg-backdrop {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-container.lg-dragging-vertical .lg-css3 .lg-item.lg-current {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
opacity: 1;
}
.lg-inline .lg-backdrop,
.lg-inline .lg-outer {
position: absolute;
}
.lg-inline .lg-backdrop {
z-index: 1;
}
.lg-inline .lg-outer {
z-index: 2;
}
.lg-inline .lg-maximize:after {
content: '';
}
.lg-components {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
will-change: transform;
-webkit-transition: -webkit-transform 0.35s ease-out 0s;
-moz-transition: -moz-transform 0.35s ease-out 0s;
-o-transition: -o-transform 0.35s ease-out 0s;
transition: transform 0.35s ease-out 0s;
z-index: 1080;
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
/* ========================================================================= */
/* below is a customized version of lightgallery thumbnail plugin css */
/* ========================================================================= */
.lg-outer .lg-thumb-outer {
width: 100%;
max-height: 350px;
overflow: hidden;
float: left;
}
.lg-outer .lg-thumb-outer.lg-grab .lg-thumb-item {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: -o-grab;
cursor: -ms-grab;
cursor: grab;
}
.lg-outer .lg-thumb-outer.lg-grabbing .lg-thumb-item {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: -o-grabbing;
cursor: -ms-grabbing;
cursor: grabbing;
}
.lg-outer .lg-thumb-outer.lg-dragging .lg-thumb {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-outer .lg-thumb-outer.lg-rebuilding-thumbnails .lg-thumb {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-middle {
text-align: center;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-left {
text-align: left;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-right {
text-align: right;
}
.lg-outer.lg-single-item .lg-thumb-outer {
display: none;
}
.lg-outer .lg-thumb {
padding: 5px 0;
height: 100%;
margin-bottom: -5px;
display: inline-block;
vertical-align: middle;
}
@media (min-width: 768px) {
.lg-outer .lg-thumb {
padding: 10px 0;
}
}
.lg-outer .lg-thumb-item {
cursor: pointer;
float: left;
overflow: hidden;
height: 100%;
border-radius: 2px;
outline-color: rgba(255, 255, 255, 0);
border: 2px solid rgba(255, 255, 255, 0);
margin-bottom: 5px;
will-change: outline-color;
}
@media (min-width: 768px) {
.lg-outer .lg-thumb-item {
border-radius: 4px;
outline-color: rgba(255, 255, 255, 0);
border: 2px solid rgba(255, 255, 255, 0);
-webkit-transition: outline-color 0.25s ease;
-o-transition: outline-color 0.25s ease;
transition: outline-color 0.25s ease;
}
}
.lg-outer .lg-thumb-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.lg-outer.lg-can-toggle .lg-item {
padding-bottom: 0;
}
.lg-outer .lg-toggle-thumb:after {
content: '';
}
.lg-outer.lg-animate-thumb .lg-thumb {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
/* ========================================================================= */
/* custom button for global search */
/* ========================================================================= */
.lg-toolbar .btn-glob-search {
background-position: center;
background-repeat: no-repeat;
opacity: 0.6;
}
.lg-toolbar .btn-glob-search:hover {
opacity: 1;
}
.lg-toolbar .btn-glob-search:after {
content: '';
}