Updated non-index pages to new code for tile submenus.
This commit is contained in:
parent
814e7a89ce
commit
c4a5bff4a3
4 changed files with 140 additions and 108 deletions
|
|
@ -1,8 +1,13 @@
|
||||||
{
|
{
|
||||||
"brand": "LASER EVERYTHING",
|
"brand": "LASER EVERYTHING",
|
||||||
"grid": { "gap": 12, "fit": "stretch" },
|
"grid": {
|
||||||
"reveal": { "durationMs": 2600, "revertDelayMs": 700 },
|
"gap": 12,
|
||||||
|
"fit": "stretch"
|
||||||
|
},
|
||||||
|
"reveal": {
|
||||||
|
"durationMs": 2600,
|
||||||
|
"revertDelayMs": 700
|
||||||
|
},
|
||||||
"hint": {
|
"hint": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rows": 24,
|
"rows": 24,
|
||||||
|
|
@ -11,12 +16,13 @@
|
||||||
"opacity": 0.02,
|
"opacity": 0.02,
|
||||||
"angleDeg": -12,
|
"angleDeg": -12,
|
||||||
"rowGapPx": 8,
|
"rowGapPx": 8,
|
||||||
"spacing": "\u00A0",
|
"spacing": "\u00a0",
|
||||||
"planeScale": 3,
|
"planeScale": 3,
|
||||||
"offsetAmpPct": 35,
|
"offsetAmpPct": 35,
|
||||||
"featherPct": 0
|
"featherPct": 0
|
||||||
},
|
},
|
||||||
|
"layout": { "respectConfigOrder": false
|
||||||
|
},
|
||||||
"tiles": [
|
"tiles": [
|
||||||
{
|
{
|
||||||
"title": "YouTube",
|
"title": "YouTube",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
{
|
{
|
||||||
"brand": "LASER EVERYTHING",
|
"brand": "LASER EVERYTHING",
|
||||||
"grid": { "gap": 12, "fit": "stretch" },
|
"grid": {
|
||||||
"reveal": { "durationMs": 2600, "revertDelayMs": 700 },
|
"gap": 12,
|
||||||
|
"fit": "stretch"
|
||||||
|
},
|
||||||
|
"reveal": {
|
||||||
|
"durationMs": 2600,
|
||||||
|
"revertDelayMs": 700
|
||||||
|
},
|
||||||
"hint": {
|
"hint": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rows": 24,
|
"rows": 24,
|
||||||
|
|
@ -11,12 +16,13 @@
|
||||||
"opacity": 0.02,
|
"opacity": 0.02,
|
||||||
"angleDeg": -12,
|
"angleDeg": -12,
|
||||||
"rowGapPx": 8,
|
"rowGapPx": 8,
|
||||||
"spacing": "\u00A0",
|
"spacing": "\u00a0",
|
||||||
"planeScale": 3,
|
"planeScale": 3,
|
||||||
"offsetAmpPct": 35,
|
"offsetAmpPct": 35,
|
||||||
"featherPct": 0
|
"featherPct": 0
|
||||||
},
|
},
|
||||||
|
"layout": { "respectConfigOrder": false
|
||||||
|
},
|
||||||
"tiles": [
|
"tiles": [
|
||||||
{
|
{
|
||||||
"title": "Laser Master Academy",
|
"title": "Laser Master Academy",
|
||||||
|
|
|
||||||
108
social.html
108
social.html
|
|
@ -17,10 +17,8 @@
|
||||||
<script>
|
<script>
|
||||||
/* ======================================================
|
/* ======================================================
|
||||||
Deterministic tiles + “wrapping paper” hints (SVG-free)
|
Deterministic tiles + “wrapping paper” hints (SVG-free)
|
||||||
Hint rows are long text lines on a huge plane; tile clips.
|
|
||||||
Config default: /configs/index.json (override with ?config=name)
|
Config default: /configs/index.json (override with ?config=name)
|
||||||
====================================================== */
|
====================================================== */
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
/* ---------- tiny seeded RNG (deterministic) ---------- */
|
/* ---------- tiny seeded RNG (deterministic) ---------- */
|
||||||
function xmur3(str){
|
function xmur3(str){
|
||||||
|
|
@ -56,26 +54,25 @@
|
||||||
return u.pathname + u.search;
|
return u.pathname + u.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default config (simple & clear). Your JSON can override any/all.
|
// Default config (your JSON can override any/all).
|
||||||
let cfg = {
|
let cfg = {
|
||||||
brand: "LASER EVERYTHING",
|
brand: "LASER EVERYTHING",
|
||||||
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
|
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
|
||||||
reveal: { durationMs: 2600, revertDelayMs: 700 },
|
reveal: { durationMs: 2600, revertDelayMs: 700 },
|
||||||
|
|
||||||
hint: {
|
hint: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
rows: 5, // number of rows
|
rows: 5,
|
||||||
textCase: "upper", // 'upper' | 'lower' | 'none'
|
textCase: "upper",
|
||||||
fontPx: 16, // base size in px
|
fontPx: 16,
|
||||||
opacity: 0.12,
|
opacity: 0.12,
|
||||||
angleDeg: -12, // rotation of the hint plane
|
angleDeg: -12,
|
||||||
rowGapPx: 8, // vertical gap between rows
|
rowGapPx: 8,
|
||||||
spacing: "\u00A0\u00A0\u00A0",// between repeats (use NBSP or your string)
|
spacing: "\u00A0\u00A0\u00A0",
|
||||||
planeScale: 3, // plane size vs tile (3 = 300% both axes)
|
planeScale: 3,
|
||||||
offsetAmpPct: 35, // max per-row horizontal offset (% of tile width)
|
offsetAmpPct: 35,
|
||||||
featherPct: 0 // 0..10 soft mask on extreme left/right (optional)
|
featherPct: 0
|
||||||
},
|
},
|
||||||
|
layout: { respectConfigOrder: false }, // <— override knob
|
||||||
tiles: []
|
tiles: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,7 +85,7 @@
|
||||||
|
|
||||||
/* ---------- apply brand + CSS vars ---------- */
|
/* ---------- apply brand + CSS vars ---------- */
|
||||||
if (cfg.brand) document.querySelector('.brand').textContent = cfg.brand;
|
if (cfg.brand) document.querySelector('.brand').textContent = cfg.brand;
|
||||||
if (cfg.grid?.gap != null) document.documentElement.style.setProperty('--gap', (cfg.grid.gap|0)+'px');
|
if (cfg.grid?.gap != null) document.documentElement.style.setProperty('--gap', (cfg.grid.gap|0)+'px');
|
||||||
if (cfg.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
|
if (cfg.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
|
||||||
|
|
||||||
// Hint CSS vars
|
// Hint CSS vars
|
||||||
|
|
@ -98,7 +95,8 @@
|
||||||
root.style.setProperty('--hint-angle', (H.angleDeg!=null ? H.angleDeg : -12) + 'deg');
|
root.style.setProperty('--hint-angle', (H.angleDeg!=null ? H.angleDeg : -12) + 'deg');
|
||||||
root.style.setProperty('--hint-size', (H.fontPx!=null ? (H.fontPx|0)+'px' : '16px'));
|
root.style.setProperty('--hint-size', (H.fontPx!=null ? (H.fontPx|0)+'px' : '16px'));
|
||||||
root.style.setProperty('--hint-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
|
root.style.setProperty('--hint-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
|
||||||
root.style.setProperty('--hint-plane', (H.planeScale!=null ? +H.planeScale : 3));
|
root.style.setProperty('--hint-plane', String(H.planeScale!=null ? +H.planeScale : 3));
|
||||||
|
root.style.setProperty('--hint-feather', (H.featherPct!=null ? +H.featherPct : 0) + '%');
|
||||||
|
|
||||||
const grid = document.getElementById('grid');
|
const grid = document.getElementById('grid');
|
||||||
|
|
||||||
|
|
@ -118,14 +116,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- build tiles ---------- */
|
/* ---------- build tiles ---------- */
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
const tiles = [];
|
const tiles = [];
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
|
||||||
(cfg.tiles||[]).forEach((t,i)=>{
|
(cfg.tiles||[]).forEach((t,i)=>{
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.className='tile';
|
a.className='tile';
|
||||||
a.href = t.href || '#';
|
a.href = t.href || '#';
|
||||||
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
|
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
|
||||||
|
|
||||||
|
/* Use config color everywhere (prod aesthetic) */
|
||||||
if (t.color) a.style.setProperty('--color', t.color);
|
if (t.color) a.style.setProperty('--color', t.color);
|
||||||
|
|
||||||
if (t.image){
|
if (t.image){
|
||||||
|
|
@ -136,10 +136,10 @@
|
||||||
a.style.backgroundPosition=t.bgPos||'center';
|
a.style.backgroundPosition=t.bgPos||'center';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe cover
|
// Cover (wipe)
|
||||||
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
|
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
|
||||||
|
|
||||||
// ===== Hint layer (novel approach) =====
|
// Hint layer (above cover; visible when hidden)
|
||||||
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
|
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
|
||||||
const hintLayer = document.createElement('div');
|
const hintLayer = document.createElement('div');
|
||||||
hintLayer.className = 'hint-layer';
|
hintLayer.className = 'hint-layer';
|
||||||
|
|
@ -149,35 +149,28 @@
|
||||||
|
|
||||||
const stack = document.createElement('div');
|
const stack = document.createElement('div');
|
||||||
stack.className = 'hint-stack';
|
stack.className = 'hint-stack';
|
||||||
stack.style.setProperty('--plane-scale', String(cfg.hint?.planeScale ?? 3));
|
|
||||||
hintLayer.appendChild(stack);
|
hintLayer.appendChild(stack);
|
||||||
|
|
||||||
// Prepare deterministic RNG per tile
|
|
||||||
const seed = 'row|' + (t.title || ('tile'+i));
|
const seed = 'row|' + (t.title || ('tile'+i));
|
||||||
const rnd = rngFrom(seed);
|
const rnd = rngFrom(seed);
|
||||||
|
|
||||||
// Compose very long content (no measuring).
|
|
||||||
const baseTitle = titleForHint(t.hintTitle || t.title || '');
|
const baseTitle = titleForHint(t.hintTitle || t.title || '');
|
||||||
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
|
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
|
||||||
const chunk = (baseTitle + spacing);
|
const chunk = (baseTitle + spacing);
|
||||||
const repeats = 240; // big enough that it ALWAYS bleeds past edges
|
const repeats = 240;
|
||||||
const lineText = chunk.repeat(repeats);
|
const lineText = chunk.repeat(repeats);
|
||||||
|
|
||||||
// Build rows, centered in the big plane, with deterministic offsets.
|
|
||||||
const rows = Math.max(1, cfg.hint?.rows|0 || 3);
|
const rows = Math.max(1, cfg.hint?.rows|0 || 3);
|
||||||
for (let r=0; r<rows; r++){
|
for (let r=0; r<rows; r++){
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'hint-row';
|
row.className = 'hint-row';
|
||||||
row.textContent = lineText;
|
row.textContent = lineText;
|
||||||
|
|
||||||
// offset (px) = (random[-1..1] * amp% * tileWidth)
|
|
||||||
// we read tile width later (in layout), so set a data attribute now:
|
|
||||||
const offs = (rnd()*2 - 1) * ((cfg.hint?.offsetAmpPct ?? 35)/100);
|
const offs = (rnd()*2 - 1) * ((cfg.hint?.offsetAmpPct ?? 35)/100);
|
||||||
row.dataset.offsetScale = String(offs); // fraction of tile width
|
row.dataset.offsetScale = String(offs);
|
||||||
|
|
||||||
stack.appendChild(row);
|
stack.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.appendChild(hintLayer);
|
a.appendChild(hintLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,24 +193,36 @@
|
||||||
const d=document.createElement('p'); d.className='desc'; d.textContent=((t.description??t.desc)??''); if(!d.textContent.trim()) d.style.display='none'; wrap.appendChild(d);
|
const d=document.createElement('p'); d.className='desc'; d.textContent=((t.description??t.desc)??''); if(!d.textContent.trim()) d.style.display='none'; wrap.appendChild(d);
|
||||||
textL.appendChild(wrap); a.appendChild(textL);
|
textL.appendChild(wrap); a.appendChild(textL);
|
||||||
|
|
||||||
// Sizing
|
// Sizing (raw spans from config)
|
||||||
const span = parseSpan(t.size || 1);
|
const span = parseSpan(t.size || 1);
|
||||||
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
|
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
|
||||||
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
|
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
|
||||||
|
|
||||||
frag.appendChild(a);
|
|
||||||
tiles.push(a);
|
tiles.push(a);
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.textContent = '';
|
/* ----- ORDER: largest-first by default; allow override to keep JSON order ----- */
|
||||||
grid.appendChild(frag);
|
const respectOrder = !!(cfg.layout && cfg.layout.respectConfigOrder);
|
||||||
|
const ordered = respectOrder ? tiles.slice()
|
||||||
|
: tiles.slice().sort((a,b)=>{
|
||||||
|
const area = (+b.dataset.w * +b.dataset.h) - (+a.dataset.w * +a.dataset.h);
|
||||||
|
if (area) return area;
|
||||||
|
const ta = a.querySelector('.title')?.textContent || '';
|
||||||
|
const tb = b.querySelector('.title')?.textContent || '';
|
||||||
|
return ta.localeCompare(tb);
|
||||||
|
});
|
||||||
|
|
||||||
/* ---------- layout (header-aware, deterministic wipe) ---------- */
|
const frag2 = document.createDocumentFragment();
|
||||||
|
ordered.forEach(n => frag2.appendChild(n));
|
||||||
|
grid.textContent = '';
|
||||||
|
grid.appendChild(frag2);
|
||||||
|
|
||||||
|
/* ---------- layout (header-aware) ---------- */
|
||||||
function layout(){
|
function layout(){
|
||||||
const W = innerWidth;
|
const W = innerWidth;
|
||||||
const H = innerHeight;
|
const H = innerHeight;
|
||||||
const V = visualViewport;
|
const VV = visualViewport;
|
||||||
const vh = (V && V.height) ? V.height : H;
|
const vh = (VV && VV.height) ? VV.height : H;
|
||||||
|
|
||||||
const header = document.getElementById('siteHeader');
|
const header = document.getElementById('siteHeader');
|
||||||
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
|
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
|
||||||
|
|
@ -225,7 +230,7 @@
|
||||||
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
|
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
|
||||||
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
|
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
|
||||||
|
|
||||||
const S = tiles.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
|
const S = ordered.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
|
||||||
const A = W / (HH || 1);
|
const A = W / (HH || 1);
|
||||||
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
|
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
|
||||||
let rows = Math.max(1, Math.ceil(S / cols));
|
let rows = Math.max(1, Math.ceil(S / cols));
|
||||||
|
|
@ -235,11 +240,13 @@
|
||||||
Math.floor((HH - gap*(r+1)) / r)
|
Math.floor((HH - gap*(r+1)) / r)
|
||||||
));
|
));
|
||||||
|
|
||||||
let best = { cols, rows, unit: unitFor(cols, rows) };
|
let best = { cols, rows, unit: unitFor(cols, rows), holes: Math.max(0, cols*rows - S) };
|
||||||
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
|
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
|
||||||
const r = Math.max(1, Math.ceil(S / c));
|
const r = Math.max(1, Math.ceil(S / c));
|
||||||
const u = unitFor(c, r);
|
const u = unitFor(c, r);
|
||||||
if (u > best.unit || (u === best.unit && r < best.rows)) best = { cols:c, rows:r, unit:u };
|
const h = Math.max(0, c*r - S);
|
||||||
|
const better = (u > best.unit) || (u === best.unit && h < best.holes) || (u === best.unit && h === best.holes && r < best.rows);
|
||||||
|
if (better) best = { cols:c, rows:r, unit:u, holes:h };
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.style.gap = gap + 'px';
|
grid.style.gap = gap + 'px';
|
||||||
|
|
@ -255,11 +262,15 @@
|
||||||
grid.style.gridAutoRows = `${best.unit}px`;
|
grid.style.gridAutoRows = `${best.unit}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles.forEach(n=>{
|
ordered.forEach(n=>{
|
||||||
n.style.gridColumn = `span ${n.dataset.w}`;
|
/* Phone-only span clamp to keep everything on one page */
|
||||||
n.style.gridRow = `span ${n.dataset.h}`;
|
const isPhone = innerWidth <= 520;
|
||||||
|
const w = Math.min(+n.dataset.w, isPhone ? 2 : +n.dataset.w);
|
||||||
|
const h = Math.min(+n.dataset.h, isPhone ? 2 : +n.dataset.h);
|
||||||
|
n.style.gridColumn = `span ${w}`;
|
||||||
|
n.style.gridRow = `span ${h}`;
|
||||||
|
|
||||||
// Resolve deterministic wipe dir (seeded by title)
|
/* Deterministic wipe dir (prod = 140%) */
|
||||||
const title = n.querySelector('.title')?.textContent || '';
|
const title = n.querySelector('.title')?.textContent || '';
|
||||||
const R = rngFrom('wipe|'+title);
|
const R = rngFrom('wipe|'+title);
|
||||||
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
|
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
|
||||||
|
|
@ -267,12 +278,11 @@
|
||||||
n.style.setProperty('--dx', (dx*140)+'%');
|
n.style.setProperty('--dx', (dx*140)+'%');
|
||||||
n.style.setProperty('--dy', (dy*140)+'%');
|
n.style.setProperty('--dy', (dy*140)+'%');
|
||||||
|
|
||||||
// Update row pixel offsets based on current tile width
|
/* Recompute hint row offsets using current tile width */
|
||||||
const tw = n.getBoundingClientRect().width;
|
const tw = n.getBoundingClientRect().width;
|
||||||
n.querySelectorAll('.hint-row').forEach(row=>{
|
n.querySelectorAll('.hint-row').forEach(row=>{
|
||||||
const scale = parseFloat(row.dataset.offsetScale || '0'); // [-1..1] * amp%
|
const scale = parseFloat(row.dataset.offsetScale || '0');
|
||||||
const px = scale * tw; // px offset
|
row.style.setProperty('--row-offset-px', (scale * tw) + 'px');
|
||||||
row.style.setProperty('--row-offset-px', px + 'px');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +292,7 @@
|
||||||
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
|
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
|
||||||
|
|
||||||
// JS-assisted reveal class only
|
// JS-assisted reveal class only
|
||||||
tiles.forEach(tile=>{
|
ordered.forEach(tile=>{
|
||||||
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
|
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
|
||||||
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
|
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
|
||||||
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
|
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
|
||||||
|
|
|
||||||
108
support.html
108
support.html
|
|
@ -17,10 +17,8 @@
|
||||||
<script>
|
<script>
|
||||||
/* ======================================================
|
/* ======================================================
|
||||||
Deterministic tiles + “wrapping paper” hints (SVG-free)
|
Deterministic tiles + “wrapping paper” hints (SVG-free)
|
||||||
Hint rows are long text lines on a huge plane; tile clips.
|
|
||||||
Config default: /configs/index.json (override with ?config=name)
|
Config default: /configs/index.json (override with ?config=name)
|
||||||
====================================================== */
|
====================================================== */
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
/* ---------- tiny seeded RNG (deterministic) ---------- */
|
/* ---------- tiny seeded RNG (deterministic) ---------- */
|
||||||
function xmur3(str){
|
function xmur3(str){
|
||||||
|
|
@ -56,26 +54,25 @@
|
||||||
return u.pathname + u.search;
|
return u.pathname + u.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default config (simple & clear). Your JSON can override any/all.
|
// Default config (your JSON can override any/all).
|
||||||
let cfg = {
|
let cfg = {
|
||||||
brand: "LASER EVERYTHING",
|
brand: "LASER EVERYTHING",
|
||||||
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
|
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
|
||||||
reveal: { durationMs: 2600, revertDelayMs: 700 },
|
reveal: { durationMs: 2600, revertDelayMs: 700 },
|
||||||
|
|
||||||
hint: {
|
hint: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
rows: 5, // number of rows
|
rows: 5,
|
||||||
textCase: "upper", // 'upper' | 'lower' | 'none'
|
textCase: "upper",
|
||||||
fontPx: 16, // base size in px
|
fontPx: 16,
|
||||||
opacity: 0.12,
|
opacity: 0.12,
|
||||||
angleDeg: -12, // rotation of the hint plane
|
angleDeg: -12,
|
||||||
rowGapPx: 8, // vertical gap between rows
|
rowGapPx: 8,
|
||||||
spacing: "\u00A0\u00A0\u00A0",// between repeats (use NBSP or your string)
|
spacing: "\u00A0\u00A0\u00A0",
|
||||||
planeScale: 3, // plane size vs tile (3 = 300% both axes)
|
planeScale: 3,
|
||||||
offsetAmpPct: 35, // max per-row horizontal offset (% of tile width)
|
offsetAmpPct: 35,
|
||||||
featherPct: 0 // 0..10 soft mask on extreme left/right (optional)
|
featherPct: 0
|
||||||
},
|
},
|
||||||
|
layout: { respectConfigOrder: false }, // <— override knob
|
||||||
tiles: []
|
tiles: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,7 +85,7 @@
|
||||||
|
|
||||||
/* ---------- apply brand + CSS vars ---------- */
|
/* ---------- apply brand + CSS vars ---------- */
|
||||||
if (cfg.brand) document.querySelector('.brand').textContent = cfg.brand;
|
if (cfg.brand) document.querySelector('.brand').textContent = cfg.brand;
|
||||||
if (cfg.grid?.gap != null) document.documentElement.style.setProperty('--gap', (cfg.grid.gap|0)+'px');
|
if (cfg.grid?.gap != null) document.documentElement.style.setProperty('--gap', (cfg.grid.gap|0)+'px');
|
||||||
if (cfg.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
|
if (cfg.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
|
||||||
|
|
||||||
// Hint CSS vars
|
// Hint CSS vars
|
||||||
|
|
@ -98,7 +95,8 @@
|
||||||
root.style.setProperty('--hint-angle', (H.angleDeg!=null ? H.angleDeg : -12) + 'deg');
|
root.style.setProperty('--hint-angle', (H.angleDeg!=null ? H.angleDeg : -12) + 'deg');
|
||||||
root.style.setProperty('--hint-size', (H.fontPx!=null ? (H.fontPx|0)+'px' : '16px'));
|
root.style.setProperty('--hint-size', (H.fontPx!=null ? (H.fontPx|0)+'px' : '16px'));
|
||||||
root.style.setProperty('--hint-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
|
root.style.setProperty('--hint-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
|
||||||
root.style.setProperty('--hint-plane', (H.planeScale!=null ? +H.planeScale : 3));
|
root.style.setProperty('--hint-plane', String(H.planeScale!=null ? +H.planeScale : 3));
|
||||||
|
root.style.setProperty('--hint-feather', (H.featherPct!=null ? +H.featherPct : 0) + '%');
|
||||||
|
|
||||||
const grid = document.getElementById('grid');
|
const grid = document.getElementById('grid');
|
||||||
|
|
||||||
|
|
@ -118,14 +116,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- build tiles ---------- */
|
/* ---------- build tiles ---------- */
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
const tiles = [];
|
const tiles = [];
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
|
||||||
(cfg.tiles||[]).forEach((t,i)=>{
|
(cfg.tiles||[]).forEach((t,i)=>{
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.className='tile';
|
a.className='tile';
|
||||||
a.href = t.href || '#';
|
a.href = t.href || '#';
|
||||||
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
|
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
|
||||||
|
|
||||||
|
/* Use config color everywhere (prod aesthetic) */
|
||||||
if (t.color) a.style.setProperty('--color', t.color);
|
if (t.color) a.style.setProperty('--color', t.color);
|
||||||
|
|
||||||
if (t.image){
|
if (t.image){
|
||||||
|
|
@ -136,10 +136,10 @@
|
||||||
a.style.backgroundPosition=t.bgPos||'center';
|
a.style.backgroundPosition=t.bgPos||'center';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe cover
|
// Cover (wipe)
|
||||||
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
|
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
|
||||||
|
|
||||||
// ===== Hint layer (novel approach) =====
|
// Hint layer (above cover; visible when hidden)
|
||||||
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
|
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
|
||||||
const hintLayer = document.createElement('div');
|
const hintLayer = document.createElement('div');
|
||||||
hintLayer.className = 'hint-layer';
|
hintLayer.className = 'hint-layer';
|
||||||
|
|
@ -149,35 +149,28 @@
|
||||||
|
|
||||||
const stack = document.createElement('div');
|
const stack = document.createElement('div');
|
||||||
stack.className = 'hint-stack';
|
stack.className = 'hint-stack';
|
||||||
stack.style.setProperty('--plane-scale', String(cfg.hint?.planeScale ?? 3));
|
|
||||||
hintLayer.appendChild(stack);
|
hintLayer.appendChild(stack);
|
||||||
|
|
||||||
// Prepare deterministic RNG per tile
|
|
||||||
const seed = 'row|' + (t.title || ('tile'+i));
|
const seed = 'row|' + (t.title || ('tile'+i));
|
||||||
const rnd = rngFrom(seed);
|
const rnd = rngFrom(seed);
|
||||||
|
|
||||||
// Compose very long content (no measuring).
|
|
||||||
const baseTitle = titleForHint(t.hintTitle || t.title || '');
|
const baseTitle = titleForHint(t.hintTitle || t.title || '');
|
||||||
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
|
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
|
||||||
const chunk = (baseTitle + spacing);
|
const chunk = (baseTitle + spacing);
|
||||||
const repeats = 240; // big enough that it ALWAYS bleeds past edges
|
const repeats = 240;
|
||||||
const lineText = chunk.repeat(repeats);
|
const lineText = chunk.repeat(repeats);
|
||||||
|
|
||||||
// Build rows, centered in the big plane, with deterministic offsets.
|
|
||||||
const rows = Math.max(1, cfg.hint?.rows|0 || 3);
|
const rows = Math.max(1, cfg.hint?.rows|0 || 3);
|
||||||
for (let r=0; r<rows; r++){
|
for (let r=0; r<rows; r++){
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'hint-row';
|
row.className = 'hint-row';
|
||||||
row.textContent = lineText;
|
row.textContent = lineText;
|
||||||
|
|
||||||
// offset (px) = (random[-1..1] * amp% * tileWidth)
|
|
||||||
// we read tile width later (in layout), so set a data attribute now:
|
|
||||||
const offs = (rnd()*2 - 1) * ((cfg.hint?.offsetAmpPct ?? 35)/100);
|
const offs = (rnd()*2 - 1) * ((cfg.hint?.offsetAmpPct ?? 35)/100);
|
||||||
row.dataset.offsetScale = String(offs); // fraction of tile width
|
row.dataset.offsetScale = String(offs);
|
||||||
|
|
||||||
stack.appendChild(row);
|
stack.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.appendChild(hintLayer);
|
a.appendChild(hintLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,24 +193,36 @@
|
||||||
const d=document.createElement('p'); d.className='desc'; d.textContent=((t.description??t.desc)??''); if(!d.textContent.trim()) d.style.display='none'; wrap.appendChild(d);
|
const d=document.createElement('p'); d.className='desc'; d.textContent=((t.description??t.desc)??''); if(!d.textContent.trim()) d.style.display='none'; wrap.appendChild(d);
|
||||||
textL.appendChild(wrap); a.appendChild(textL);
|
textL.appendChild(wrap); a.appendChild(textL);
|
||||||
|
|
||||||
// Sizing
|
// Sizing (raw spans from config)
|
||||||
const span = parseSpan(t.size || 1);
|
const span = parseSpan(t.size || 1);
|
||||||
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
|
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
|
||||||
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
|
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
|
||||||
|
|
||||||
frag.appendChild(a);
|
|
||||||
tiles.push(a);
|
tiles.push(a);
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.textContent = '';
|
/* ----- ORDER: largest-first by default; allow override to keep JSON order ----- */
|
||||||
grid.appendChild(frag);
|
const respectOrder = !!(cfg.layout && cfg.layout.respectConfigOrder);
|
||||||
|
const ordered = respectOrder ? tiles.slice()
|
||||||
|
: tiles.slice().sort((a,b)=>{
|
||||||
|
const area = (+b.dataset.w * +b.dataset.h) - (+a.dataset.w * +a.dataset.h);
|
||||||
|
if (area) return area;
|
||||||
|
const ta = a.querySelector('.title')?.textContent || '';
|
||||||
|
const tb = b.querySelector('.title')?.textContent || '';
|
||||||
|
return ta.localeCompare(tb);
|
||||||
|
});
|
||||||
|
|
||||||
/* ---------- layout (header-aware, deterministic wipe) ---------- */
|
const frag2 = document.createDocumentFragment();
|
||||||
|
ordered.forEach(n => frag2.appendChild(n));
|
||||||
|
grid.textContent = '';
|
||||||
|
grid.appendChild(frag2);
|
||||||
|
|
||||||
|
/* ---------- layout (header-aware) ---------- */
|
||||||
function layout(){
|
function layout(){
|
||||||
const W = innerWidth;
|
const W = innerWidth;
|
||||||
const H = innerHeight;
|
const H = innerHeight;
|
||||||
const V = visualViewport;
|
const VV = visualViewport;
|
||||||
const vh = (V && V.height) ? V.height : H;
|
const vh = (VV && VV.height) ? VV.height : H;
|
||||||
|
|
||||||
const header = document.getElementById('siteHeader');
|
const header = document.getElementById('siteHeader');
|
||||||
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
|
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
|
||||||
|
|
@ -225,7 +230,7 @@
|
||||||
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
|
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
|
||||||
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
|
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
|
||||||
|
|
||||||
const S = tiles.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
|
const S = ordered.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
|
||||||
const A = W / (HH || 1);
|
const A = W / (HH || 1);
|
||||||
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
|
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
|
||||||
let rows = Math.max(1, Math.ceil(S / cols));
|
let rows = Math.max(1, Math.ceil(S / cols));
|
||||||
|
|
@ -235,11 +240,13 @@
|
||||||
Math.floor((HH - gap*(r+1)) / r)
|
Math.floor((HH - gap*(r+1)) / r)
|
||||||
));
|
));
|
||||||
|
|
||||||
let best = { cols, rows, unit: unitFor(cols, rows) };
|
let best = { cols, rows, unit: unitFor(cols, rows), holes: Math.max(0, cols*rows - S) };
|
||||||
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
|
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
|
||||||
const r = Math.max(1, Math.ceil(S / c));
|
const r = Math.max(1, Math.ceil(S / c));
|
||||||
const u = unitFor(c, r);
|
const u = unitFor(c, r);
|
||||||
if (u > best.unit || (u === best.unit && r < best.rows)) best = { cols:c, rows:r, unit:u };
|
const h = Math.max(0, c*r - S);
|
||||||
|
const better = (u > best.unit) || (u === best.unit && h < best.holes) || (u === best.unit && h === best.holes && r < best.rows);
|
||||||
|
if (better) best = { cols:c, rows:r, unit:u, holes:h };
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.style.gap = gap + 'px';
|
grid.style.gap = gap + 'px';
|
||||||
|
|
@ -255,11 +262,15 @@
|
||||||
grid.style.gridAutoRows = `${best.unit}px`;
|
grid.style.gridAutoRows = `${best.unit}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles.forEach(n=>{
|
ordered.forEach(n=>{
|
||||||
n.style.gridColumn = `span ${n.dataset.w}`;
|
/* Phone-only span clamp to keep everything on one page */
|
||||||
n.style.gridRow = `span ${n.dataset.h}`;
|
const isPhone = innerWidth <= 520;
|
||||||
|
const w = Math.min(+n.dataset.w, isPhone ? 2 : +n.dataset.w);
|
||||||
|
const h = Math.min(+n.dataset.h, isPhone ? 2 : +n.dataset.h);
|
||||||
|
n.style.gridColumn = `span ${w}`;
|
||||||
|
n.style.gridRow = `span ${h}`;
|
||||||
|
|
||||||
// Resolve deterministic wipe dir (seeded by title)
|
/* Deterministic wipe dir (prod = 140%) */
|
||||||
const title = n.querySelector('.title')?.textContent || '';
|
const title = n.querySelector('.title')?.textContent || '';
|
||||||
const R = rngFrom('wipe|'+title);
|
const R = rngFrom('wipe|'+title);
|
||||||
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
|
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
|
||||||
|
|
@ -267,12 +278,11 @@
|
||||||
n.style.setProperty('--dx', (dx*140)+'%');
|
n.style.setProperty('--dx', (dx*140)+'%');
|
||||||
n.style.setProperty('--dy', (dy*140)+'%');
|
n.style.setProperty('--dy', (dy*140)+'%');
|
||||||
|
|
||||||
// Update row pixel offsets based on current tile width
|
/* Recompute hint row offsets using current tile width */
|
||||||
const tw = n.getBoundingClientRect().width;
|
const tw = n.getBoundingClientRect().width;
|
||||||
n.querySelectorAll('.hint-row').forEach(row=>{
|
n.querySelectorAll('.hint-row').forEach(row=>{
|
||||||
const scale = parseFloat(row.dataset.offsetScale || '0'); // [-1..1] * amp%
|
const scale = parseFloat(row.dataset.offsetScale || '0');
|
||||||
const px = scale * tw; // px offset
|
row.style.setProperty('--row-offset-px', (scale * tw) + 'px');
|
||||||
row.style.setProperty('--row-offset-px', px + 'px');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +292,7 @@
|
||||||
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
|
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
|
||||||
|
|
||||||
// JS-assisted reveal class only
|
// JS-assisted reveal class only
|
||||||
tiles.forEach(tile=>{
|
ordered.forEach(tile=>{
|
||||||
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
|
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
|
||||||
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
|
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
|
||||||
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
|
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue