Updates, inlining, fix for container restarts
This commit is contained in:
@@ -1 +1,6 @@
|
|||||||
|
.pnpm-store
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
dist
|
||||||
15
.prettierrc
15
.prettierrc
@@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
"semi": true,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"tabWidth": 2,
|
"tabWidth": 4,
|
||||||
"trailingComma": "es5"
|
"trailingComma": "none",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.yml",
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"proseWrap": "preserve"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["stylelint-config-recommended"],
|
|
||||||
"ignoreFiles": ["node_modules/**"]
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,14 @@ depends_on:
|
|||||||
- ci
|
- ci
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Site build
|
||||||
|
image: node:22-alpine
|
||||||
|
commands:
|
||||||
|
- corepack enable
|
||||||
|
- corepack prepare pnpm@10.29.2 --activate
|
||||||
|
- pnpm install --frozen-lockfile
|
||||||
|
- pnpm build
|
||||||
|
|
||||||
- name: Docker image build
|
- name: Docker image build
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -1,10 +1,4 @@
|
|||||||
# Armandine gallery – static site served by Nginx
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
# Copy static site into default Nginx docroot
|
COPY nginx/conf.d/ /etc/nginx/conf.d/
|
||||||
COPY src/ /usr/share/nginx/html/
|
COPY dist/ /usr/share/nginx/html/
|
||||||
|
|
||||||
# Optional: custom nginx config could be COPY'd here
|
|
||||||
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ services:
|
|||||||
mifi-holdings-armandine-gallery:
|
mifi-holdings-armandine-gallery:
|
||||||
image: git.mifi.dev/mifi-holdings/armandine:latest
|
image: git.mifi.dev/mifi-holdings/armandine:latest
|
||||||
container_name: mifi-holdings-armandine
|
container_name: mifi-holdings-armandine
|
||||||
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- marina-net
|
- marina-net
|
||||||
labels:
|
labels:
|
||||||
@@ -9,7 +10,7 @@ services:
|
|||||||
- "traefik.docker.network=marina-net"
|
- "traefik.docker.network=marina-net"
|
||||||
- "traefik.http.routers.armandine-gallery.rule=Host(`armandine.mifi.holdings`)"
|
- "traefik.http.routers.armandine-gallery.rule=Host(`armandine.mifi.holdings`)"
|
||||||
- "traefik.http.routers.armandine-gallery.entrypoints=websecure"
|
- "traefik.http.routers.armandine-gallery.entrypoints=websecure"
|
||||||
- "traefik.http.routers.armandine-gallery.middlewares=security-prison@file"
|
- "traefik.http.routers.armandine-gallery.middlewares=security-supermax-with-analytics@file"
|
||||||
- "traefik.http.routers.armandine-gallery.tls=true"
|
- "traefik.http.routers.armandine-gallery.tls=true"
|
||||||
- "traefik.http.routers.armandine-gallery.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.armandine-gallery.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.services.armandine-gallery.loadbalancer.server.port=80"
|
- "traefik.http.services.armandine-gallery.loadbalancer.server.port=80"
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
import js from '@eslint/js';
|
import prettierConfig from 'eslint-config-prettier/flat'
|
||||||
import prettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
files: ['src/**/*.js'],
|
files: ['src/**/*.js'],
|
||||||
...js.configs.recommended,
|
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'script',
|
sourceType: 'script',
|
||||||
globals: {
|
globals: {
|
||||||
document: 'readonly',
|
|
||||||
window: 'readonly',
|
window: 'readonly',
|
||||||
localStorage: 'readonly',
|
document: 'readonly',
|
||||||
console: 'readonly',
|
dataLayer: 'writable'
|
||||||
fetch: 'readonly',
|
}
|
||||||
Image: 'readonly',
|
|
||||||
CustomEvent: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
prettierConfig
|
||||||
prettier,
|
]
|
||||||
];
|
|
||||||
|
|||||||
36
package.json
36
package.json
@@ -3,25 +3,35 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.29.2",
|
"packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
|
||||||
"description": "Armandine gallery – static Nginx site",
|
"description": "Armandine gallery – static Nginx site",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "docker build -t git.mifi.dev/mifi-holdings/armandine:latest .",
|
"build": "node scripts/build.js",
|
||||||
"push": "docker push git.mifi.dev/mifi-holdings/armandine:latest",
|
"docker:build": "docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .",
|
||||||
"lint": "pnpm lint:js && pnpm lint:css",
|
"docker:push": "docker push git.mifi.dev/mifi-holdings/landing:latest",
|
||||||
"lint:js": "eslint src",
|
|
||||||
"lint:css": "stylelint \"src/**/*.css\"",
|
|
||||||
"format": "prettier --write \"src/**/*.{html,css,js,json}\"",
|
"format": "prettier --write \"src/**/*.{html,css,js,json}\"",
|
||||||
"format:check": "prettier --check \"src/**/*.{html,css,js,json}\"",
|
"format:check": "prettier --check \"src/**/*.{html,css,js,json}\"",
|
||||||
"serve": "pnpm exec serve src -p 3000"
|
"lint": "pnpm run lint:yaml && pnpm run lint:js && pnpm run lint:css",
|
||||||
|
"lint:css": "stylelint \"src/**/*.css\"",
|
||||||
|
"lint:js": "eslint src/",
|
||||||
|
"lint:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml",
|
||||||
|
"lint:fix": "pnpm run lint:fix:js && pnpm run lint:fix:css && pnpm run lint:fix:yaml",
|
||||||
|
"lint:fix:js": "eslint src/ --fix",
|
||||||
|
"lint:fix:css": "stylelint \"src/**/*.css\" --fix",
|
||||||
|
"lint:fix:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml --fix",
|
||||||
|
"preview": "serve src -l 3000",
|
||||||
|
"preview:prod": "pnpm build && serve dist -l 3000"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.15.0",
|
"clean-css": "^5.3.3",
|
||||||
"eslint": "^9.15.0",
|
"beasties": "^0.4.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint": "^10.0.0",
|
||||||
"prettier": "^3.3.3",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
"serve": "^14.2.4",
|
"serve": "^14.2.4",
|
||||||
"stylelint": "^16.10.0",
|
"stylelint": "^17.3.0",
|
||||||
"stylelint-config-recommended": "^14.0.0"
|
"stylelint-config-standard": "^40.0.0",
|
||||||
|
"terser": "^5.46.0",
|
||||||
|
"yaml-lint": "^1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
784
pnpm-lock.yaml
generated
784
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
78
scripts/build.js
Normal file
78
scripts/build.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Build script: copy src → dist, minify JS/CSS, inline critical CSS (Beasties).
|
||||||
|
* Run with: pnpm build
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
rmSync,
|
||||||
|
mkdirSync,
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync,
|
||||||
|
cpSync,
|
||||||
|
readdirSync
|
||||||
|
} from 'fs'
|
||||||
|
import { join, dirname, extname } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import Beasties from 'beasties'
|
||||||
|
import { minify as minifyJs } from 'terser'
|
||||||
|
import CleanCSS from 'clean-css'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const root = join(__dirname, '..')
|
||||||
|
const srcDir = join(root, 'src')
|
||||||
|
const distDir = join(root, 'dist')
|
||||||
|
|
||||||
|
function getFiles(dir, files = []) {
|
||||||
|
const entries = readdirSync(dir, { withFileTypes: true })
|
||||||
|
for (const e of entries) {
|
||||||
|
const full = join(dir, e.name)
|
||||||
|
if (e.isDirectory()) getFiles(full, files)
|
||||||
|
else files.push(full)
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. Clean and copy src → dist
|
||||||
|
rmSync(distDir, { recursive: true, force: true })
|
||||||
|
mkdirSync(distDir, { recursive: true })
|
||||||
|
cpSync(srcDir, distDir, { recursive: true })
|
||||||
|
|
||||||
|
const distFiles = getFiles(distDir)
|
||||||
|
|
||||||
|
// 2. Minify JS
|
||||||
|
const jsFiles = distFiles.filter((f) => extname(f) === '.js')
|
||||||
|
for (const f of jsFiles) {
|
||||||
|
const code = readFileSync(f, 'utf8')
|
||||||
|
const result = await minifyJs(code, { format: { comments: false } })
|
||||||
|
if (result.code) writeFileSync(f, result.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Minify CSS
|
||||||
|
const cleanCss = new CleanCSS({ level: 2 })
|
||||||
|
const cssFiles = distFiles.filter((f) => extname(f) === '.css')
|
||||||
|
for (const f of cssFiles) {
|
||||||
|
const code = readFileSync(f, 'utf8')
|
||||||
|
const result = cleanCss.minify(code)
|
||||||
|
if (!result.errors.length) writeFileSync(f, result.styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Inline critical CSS with Beasties for all HTML files (no browser; works in CI)
|
||||||
|
const htmlFiles = distFiles.filter((f) => extname(f) === '.html')
|
||||||
|
const beasties = new Beasties({
|
||||||
|
path: distDir,
|
||||||
|
preload: 'default',
|
||||||
|
logLevel: 'warn'
|
||||||
|
})
|
||||||
|
for (const htmlFile of htmlFiles) {
|
||||||
|
const html = readFileSync(htmlFile, 'utf8')
|
||||||
|
const inlined = await beasties.process(html)
|
||||||
|
writeFileSync(htmlFile, inlined)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Build complete: dist/')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
BIN
src/assets/android-chrome-192x192.png
Normal file
BIN
src/assets/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
src/assets/android-chrome-512x512.png
Normal file
BIN
src/assets/android-chrome-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
BIN
src/assets/apple-touch-icon.png
Normal file
BIN
src/assets/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -3,6 +3,7 @@
|
|||||||
--fg: #222;
|
--fg: #222;
|
||||||
--accent: #007acc;
|
--accent: #007acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--bg: #111;
|
--bg: #111;
|
||||||
@@ -10,26 +11,31 @@
|
|||||||
--accent: #46c;
|
--accent: #46c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Explicit theme toggle overrides (win over media query when set) */
|
/* Explicit theme toggle overrides (win over media query when set) */
|
||||||
html.dark {
|
html.dark {
|
||||||
--bg: #111;
|
--bg: #111;
|
||||||
--fg: #eee;
|
--fg: #eee;
|
||||||
--accent: #46c;
|
--accent: #46c;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.light {
|
html.light {
|
||||||
--bg: #fff;
|
--bg: #fff;
|
||||||
--fg: #222;
|
--fg: #222;
|
||||||
--accent: #007acc;
|
--accent: #007acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightbox-open {
|
.lightbox-open {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-header {
|
.site-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -38,6 +44,7 @@ body {
|
|||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-button {
|
.emoji-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -45,54 +52,59 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-grid {
|
.gallery-grid {
|
||||||
column-count: 1;
|
column-count: 1;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
|
||||||
|
@media (width >= 768px) {
|
||||||
.gallery-grid {
|
.gallery-grid {
|
||||||
column-count: 2;
|
column-count: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 1024px) {
|
|
||||||
|
@media (width >= 1024px) {
|
||||||
.gallery-grid {
|
.gallery-grid {
|
||||||
column-count: 3;
|
column-count: 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item {
|
.gallery-item {
|
||||||
margin: 0 0 1rem;
|
margin: 0 0 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item img {
|
.gallery-item img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item figcaption {
|
.gallery-item figcaption {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgb(0 0 0 / 60%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item:focus figcaption,
|
.gallery-item:focus figcaption,
|
||||||
.gallery-item:hover figcaption {
|
.gallery-item:hover figcaption {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lightbox {
|
#lightbox {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
inset: 0;
|
||||||
left: 0;
|
background: rgb(0 0 0 / 90%);
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.9);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -101,22 +113,26 @@ body {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lightbox[aria-hidden='false'] {
|
#lightbox[aria-hidden='false'] {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lb-content img,
|
#lb-content img,
|
||||||
#lb-content video {
|
#lb-content video {
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lb-caption {
|
#lb-caption {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lb-close {
|
#lb-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
|
|||||||
BIN
src/assets/favicon-16x16.png
Normal file
BIN
src/assets/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 896 B |
BIN
src/assets/favicon-32x32.png
Normal file
BIN
src/assets/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/favicon.ico
Normal file
BIN
src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
11
src/assets/js/ga-init.js
Normal file
11
src/assets/js/ga-init.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
;(function () {
|
||||||
|
var script = document.currentScript
|
||||||
|
var id = script && script.getAttribute('data-ga-id')
|
||||||
|
if (!id) return
|
||||||
|
window.dataLayer = window.dataLayer || []
|
||||||
|
function gtag() {
|
||||||
|
window.dataLayer.push(arguments)
|
||||||
|
}
|
||||||
|
gtag('js', new Date())
|
||||||
|
gtag('config', id, { anonymize_ip: true })
|
||||||
|
})()
|
||||||
@@ -1,115 +1,115 @@
|
|||||||
// --- theme toggle ---
|
// --- theme toggle ---
|
||||||
const toggle = document.getElementById('theme-toggle');
|
const toggle = document.getElementById('theme-toggle')
|
||||||
const root = document.documentElement;
|
const root = document.documentElement
|
||||||
const saved = localStorage.getItem('dark-mode');
|
const saved = localStorage.getItem('dark-mode')
|
||||||
const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
if (saved === 'true') {
|
if (saved === 'true') {
|
||||||
root.classList.add('dark');
|
root.classList.add('dark')
|
||||||
root.classList.remove('light');
|
root.classList.remove('light')
|
||||||
} else if (saved === 'false') {
|
} else if (saved === 'false') {
|
||||||
root.classList.add('light');
|
root.classList.add('light')
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark')
|
||||||
} else {
|
} else {
|
||||||
if (sysDark) {
|
if (sysDark) {
|
||||||
root.classList.add('dark');
|
root.classList.add('dark')
|
||||||
root.classList.remove('light');
|
root.classList.remove('light')
|
||||||
} else {
|
} else {
|
||||||
root.classList.add('light');
|
root.classList.add('light')
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggle.addEventListener('click', () => {
|
toggle.addEventListener('click', () => {
|
||||||
const isDark = root.classList.contains('dark');
|
const isDark = root.classList.contains('dark')
|
||||||
root.classList.toggle('dark', !isDark);
|
root.classList.toggle('dark', !isDark)
|
||||||
root.classList.toggle('light', isDark);
|
root.classList.toggle('light', isDark)
|
||||||
localStorage.setItem('dark-mode', !isDark);
|
localStorage.setItem('dark-mode', !isDark)
|
||||||
});
|
})
|
||||||
|
|
||||||
const body = document.body;
|
const body = document.body
|
||||||
|
|
||||||
// --- lightbox base ---
|
// --- lightbox base ---
|
||||||
const lb = document.getElementById('lightbox');
|
const lb = document.getElementById('lightbox')
|
||||||
const lbCnt = document.getElementById('lb-content');
|
const lbCnt = document.getElementById('lb-content')
|
||||||
const lbCap = document.getElementById('lb-caption');
|
const lbCap = document.getElementById('lb-caption')
|
||||||
document.getElementById('lb-close').addEventListener('click', () => {
|
document.getElementById('lb-close').addEventListener('click', () => {
|
||||||
lb.setAttribute('aria-hidden', 'true');
|
lb.setAttribute('aria-hidden', 'true')
|
||||||
body.classList.remove('lightbox-open');
|
body.classList.remove('lightbox-open')
|
||||||
lbCnt.innerHTML = '';
|
lbCnt.innerHTML = ''
|
||||||
});
|
})
|
||||||
|
|
||||||
// --- build gallery ---
|
// --- build gallery ---
|
||||||
const gallery = document.getElementById('gallery');
|
const gallery = document.getElementById('gallery')
|
||||||
const mediaData = JSON.parse(document.getElementById('media-data').textContent);
|
const mediaData = JSON.parse(document.getElementById('media-data').textContent)
|
||||||
|
|
||||||
const createPicture = (item) => {
|
const createPicture = (item) => {
|
||||||
const pic = document.createElement('picture');
|
const pic = document.createElement('picture')
|
||||||
['desktop', 'tablet', 'mobile'].forEach((bp) => {
|
;['desktop', 'tablet', 'mobile'].forEach((bp) => {
|
||||||
const src = document.createElement('source');
|
const src = document.createElement('source')
|
||||||
const widthQuery = bp === 'desktop' ? 1024 : bp === 'tablet' ? 768 : 0;
|
const widthQuery = bp === 'desktop' ? 1024 : bp === 'tablet' ? 768 : 0
|
||||||
src.media = `(min-width:${widthQuery}px)`;
|
src.media = `(min-width:${widthQuery}px)`
|
||||||
if (item.type === 'image') {
|
if (item.type === 'image') {
|
||||||
src.srcset =
|
src.srcset =
|
||||||
`assets/media/${bp}/${item.name}@1x.webp 1x, ` +
|
`assets/media/${bp}/${item.name}@1x.webp 1x, ` +
|
||||||
`assets/media/${bp}/${item.name}.webp 2x`;
|
`assets/media/${bp}/${item.name}.webp 2x`
|
||||||
} else {
|
} else {
|
||||||
// video poster still
|
// video poster still
|
||||||
src.srcset =
|
src.srcset =
|
||||||
`assets/media/${bp}/${item.name}_still@1x.webp 1x, ` +
|
`assets/media/${bp}/${item.name}_still@1x.webp 1x, ` +
|
||||||
`assets/media/${bp}/${item.name}_still.webp 2x`;
|
`assets/media/${bp}/${item.name}_still.webp 2x`
|
||||||
}
|
}
|
||||||
pic.appendChild(src);
|
pic.appendChild(src)
|
||||||
});
|
})
|
||||||
|
|
||||||
// thumbnail fallback (always 300px/2×)
|
// thumbnail fallback (always 300px/2×)
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img')
|
||||||
img.src = `assets/media/thumbnail/${item.name}.webp`;
|
img.src = `assets/media/thumbnail/${item.name}.webp`
|
||||||
img.alt = item.alt.replace(/[‘’]/g, '');
|
img.alt = item.alt.replace(/[‘’]/g, '')
|
||||||
pic.appendChild(img);
|
pic.appendChild(img)
|
||||||
return pic;
|
return pic
|
||||||
};
|
}
|
||||||
|
|
||||||
mediaData.forEach((item) => {
|
mediaData.forEach((item) => {
|
||||||
const fig = document.createElement('figure');
|
const fig = document.createElement('figure')
|
||||||
fig.className = `gallery-item${item.type === 'video' ? ' video' : ''}`;
|
fig.className = `gallery-item${item.type === 'video' ? ' video' : ''}`
|
||||||
fig.tabIndex = 0;
|
fig.tabIndex = 0
|
||||||
fig.dataset.name = item.name;
|
fig.dataset.name = item.name
|
||||||
fig.dataset.type = item.type;
|
fig.dataset.type = item.type
|
||||||
fig.dataset.caption = item.caption;
|
fig.dataset.caption = item.caption
|
||||||
fig.appendChild(createPicture(item));
|
fig.appendChild(createPicture(item))
|
||||||
|
|
||||||
// overlay caption
|
// overlay caption
|
||||||
const cap = document.createElement('figcaption');
|
const cap = document.createElement('figcaption')
|
||||||
cap.textContent = item.caption;
|
cap.textContent = item.caption
|
||||||
fig.appendChild(cap);
|
fig.appendChild(cap)
|
||||||
|
|
||||||
// events
|
// events
|
||||||
fig.addEventListener('click', () => openLightbox(item));
|
fig.addEventListener('click', () => openLightbox(item))
|
||||||
fig.addEventListener(
|
fig.addEventListener(
|
||||||
'keypress',
|
'keypress',
|
||||||
(e) => e.key === 'Enter' && openLightbox(item)
|
(e) => e.key === 'Enter' && openLightbox(item)
|
||||||
);
|
)
|
||||||
|
|
||||||
gallery.appendChild(fig);
|
gallery.appendChild(fig)
|
||||||
});
|
})
|
||||||
|
|
||||||
// --- video toggle ---
|
// --- video toggle ---
|
||||||
const videoTgl = document.getElementById('show_video');
|
const videoTgl = document.getElementById('show_video')
|
||||||
videoTgl.addEventListener('click', () => {
|
videoTgl.addEventListener('click', () => {
|
||||||
openLightbox(mediaData.find((i) => i.type === 'video'));
|
openLightbox(mediaData.find((i) => i.type === 'video'))
|
||||||
});
|
})
|
||||||
|
|
||||||
function openLightbox(item) {
|
function openLightbox(item) {
|
||||||
lbCnt.innerHTML = '';
|
lbCnt.innerHTML = ''
|
||||||
if (item.type === 'video') {
|
if (item.type === 'video') {
|
||||||
const v = document.createElement('video');
|
const v = document.createElement('video')
|
||||||
v.src = `assets/media/videos/${item.name}.mp4`;
|
v.src = `assets/media/videos/${item.name}.mp4`
|
||||||
v.controls = true;
|
v.controls = true
|
||||||
v.autoplay = true;
|
v.autoplay = true
|
||||||
lbCnt.appendChild(v);
|
lbCnt.appendChild(v)
|
||||||
} else {
|
} else {
|
||||||
lbCnt.appendChild(createPicture(item));
|
lbCnt.appendChild(createPicture(item))
|
||||||
}
|
}
|
||||||
lbCap.textContent = item.caption;
|
lbCap.textContent = item.caption
|
||||||
body.classList.add('lightbox-open');
|
body.classList.add('lightbox-open')
|
||||||
lb.setAttribute('aria-hidden', 'false');
|
lb.setAttribute('aria-hidden', 'false')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src="https://www.googletagmanager.com/gtag/js?id=G-QZGFK4MDT4"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="/assets/js/ga-init.js"
|
||||||
|
data-ga-id="G-QZGFK4MDT4"
|
||||||
|
></script>
|
||||||
|
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>64 Armandine St #3 Boston, Massachusetts</title>
|
<title>64 Armandine St #3 Boston, Massachusetts</title>
|
||||||
@@ -8,16 +19,16 @@
|
|||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
sizes="32x32"
|
sizes="32x32"
|
||||||
href="assets/favicon-32x32.png"
|
href="/assets/favicon-32x32.png"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
sizes="16x16"
|
sizes="16x16"
|
||||||
href="assets/favicon-16x16.png"
|
href="/assets/favicon-16x16.png"
|
||||||
/>
|
/>
|
||||||
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
|
||||||
<link rel="stylesheet" href="assets/css/style.css" />
|
<link rel="stylesheet" href="/assets/css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
|
|||||||
8
stylelint.config.js
Normal file
8
stylelint.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
extends: ['stylelint-config-standard'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['src/**/*.css']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user