Initial import

This commit is contained in:
makearmy 2025-08-24 09:51:18 -04:00
commit a7d09c6850
51 changed files with 1580 additions and 0 deletions

103
configs/index.json Normal file
View file

@ -0,0 +1,103 @@
{
"brand": "LASER EVERYTHING",
"grid": { "gap": 12, "fit": "stretch" },
"reveal": { "durationMs": 2600, "revertDelayMs": 700 },
"hint": {
"enabled": true,
"rows": 24,
"textCase": "upper",
"fontPx": 32,
"opacity": 0.02,
"angleDeg": -12,
"rowGapPx": 8,
"spacing": "\u00A0",
"planeScale": 3,
"offsetAmpPct": 35,
"featherPct": 0
},
"tiles": [
{
"title": "Training",
"href": "https://lasereverything.net/training",
"icon": "/icons/training.png",
"color": "#2563eb",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Get one on one training from our expert staff so you can end the cycle of frustration. Start having fun and MAKING with your equipment."
},
{
"title": "Ethics",
"href": "https://lasereverything.net/ethics",
"icon": "/icons/ethics.png",
"color": "#d60202",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Our standards for safe, fair, and responsible making."
},
{
"title": "Podcasting",
"href": "https://podcast.makearmy.io",
"icon": "/icons/castopod.png",
"image": "",
"color": "#7e02d6",
"size": "1x2",
"bgFit": "cover",
"bgPos": "center",
"description": "Long-form chats on lasers, materials, and maker culture."
},
{
"title": "Buying Guide",
"href": "https://makearmy.io/buying-guide",
"icon": "/icons/bg.png",
"color": "#145221",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Curated picks with real-world testing and notes."
},
{
"title": "Starter Settings Packs",
"href": "https://masters.lasereverything.net",
"icon": "/icons/lma.png",
"color": "#0600bd",
"size": "2x1",
"bgFit": "cover",
"bgPos": "center",
"description": "Dial-in faster with tested baseline settings. 80% of the work is done for you, loosely preconverted for every wattage and lens combination."
},
{
"title": "MakeArmy",
"href": "https://makearmy.io",
"icon": "/icons/makearmy.png",
"color": "#bd006b",
"size": "2x1",
"bgFit": "cover",
"bgPos": "center",
"description": "Community tools, services, and the latest projects."
},
{
"title": "Support Us",
"href": "https://lasereverything.net/support",
"icon": "/icons/support.png",
"color": "#02bad6",
"size": "2x1",
"bgFit": "cover",
"bgPos": "center",
"description": "Help us keep guides and tools free for the community."
},
{
"title": "Social Media",
"href": "https://lasereverything.net/social",
"icon": "/icons/youtube.png",
"color": "#00a603",
"size": "1x1",
"bgFit": "cover",
"bgPos": "center",
"description": "Find us on all of the hippest global social media conglomerates!"
}
]
}

83
configs/social.json Normal file
View file

@ -0,0 +1,83 @@
{
"brand": "LASER EVERYTHING",
"grid": { "gap": 12, "fit": "stretch" },
"reveal": { "durationMs": 2600, "revertDelayMs": 700 },
"hint": {
"enabled": true,
"rows": 24,
"textCase": "upper",
"fontPx": 32,
"opacity": 0.02,
"angleDeg": -12,
"rowGapPx": 8,
"spacing": "\u00A0",
"planeScale": 3,
"offsetAmpPct": 35,
"featherPct": 0
},
"tiles": [
{
"title": "YouTube",
"href": "https://www.youtube.com/lasereverything",
"icon": "/icons/youtube.png",
"color": "#2563eb",
"size": "2x2",
"bgFit": "cover",
"bgPos": "center",
"description": "YouTube, the world's second largest search engine."
},
{
"title": "Facebook Page",
"href": "https://www.facebook.com/lasereverythingofficial/",
"icon": "/icons/facebook-p.png",
"color": "#2563eb",
"size": "2x2",
"bgFit": "cover",
"bgPos": "center",
"description": "How your represented a business before Facebook Groups were coopted."
},
{
"title": "Facebook Group",
"href": "https://www.facebook.com/groups/1047701605814427/",
"icon": "/icons/facebook-g.png",
"color": "#f39a31",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "How you represent a business now that Facebook Groups are coopted."
},
{
"title": "Instagram",
"href": "https://www.instagram.com/lasereverythingofficial/",
"icon": "/icons/instagram.png",
"color": "#0f766e",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "The highlight reels of peoples lives and businesses!"
},
{
"title": "Tiktok",
"href": "https://www.tiktok.com/@lasereverythingofficial",
"icon": "/icons/tiktok.png",
"color": "#0f766e",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Banned, unbanned, banned, unbanned - pick a legality and stick with it."
},
{
"title": "Fediverse",
"href": "https://makearmy.io",
"icon": "/icons/fediverse.png",
"image": "",
"color": "#134823",
"size": "2x1",
"bgFit": "cover",
"bgPos": "center",
"description": "We run a wide range of fediverse instances. Check out the list at MakeArmy!"
}
]
}

63
configs/support.json Normal file
View file

@ -0,0 +1,63 @@
{
"brand": "LASER EVERYTHING",
"grid": { "gap": 12, "fit": "stretch" },
"reveal": { "durationMs": 2600, "revertDelayMs": 700 },
"hint": {
"enabled": true,
"rows": 24,
"textCase": "upper",
"fontPx": 32,
"opacity": 0.02,
"angleDeg": -12,
"rowGapPx": 8,
"spacing": "\u00A0",
"planeScale": 3,
"offsetAmpPct": 35,
"featherPct": 0
},
"tiles": [
{
"title": "Laser Master Academy",
"href": "https://masters.lasereverything.net",
"icon": "/icons/lma.png",
"color": "#2563eb",
"size": "2x2",
"bgFit": "cover",
"bgPos": "center",
"description": "Support Laser Everything and hang with true laser masters to hone your skills."
},
{
"title": "Patreon",
"href": "https://www.patreon.com/LaserEverything",
"icon": "/icons/patreon.png",
"color": "#f39a31",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Mainstream supporter platform for creators."
},
{
"title": "KoFi",
"href": "https://ko-fi.com/lasereverything",
"icon": "/icons/kofi.png",
"color": "#0f766e",
"size": 1,
"bgFit": "cover",
"bgPos": "center",
"description": "Small time unmanaged funding for one time or repeated donations."
},
{
"title": "Liberapay",
"href": "https://liberapay.com/LaserEverything",
"icon": "/icons/liberapay.png",
"image": "",
"color": "#134823",
"size": "2x1",
"bgFit": "cover",
"bgPos": "center",
"description": "FOSS and Privacy First supporter platform for creators."
}
]
}

155
ethics.html Normal file
View file

@ -0,0 +1,155 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ethics · LaserEverything</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body class="page">
<div class="page-wrap">
<header class="page-header">
<a class="back-btn" href="/" id="backLink" aria-label="Back to home">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M19 12H5"></path><path d="M12 19l-7-7 7-7"></path>
</svg>
<span>Back to Home</span>
</a>
<div></div>
</header>
<main class="page-main" role="main">
<article class="article">
<h1>Ethics</h1>
<h2>Parties</h2>
<ul class="party-list">
<li><strong>Laser Everything LLC</strong> (“Laser Everything”)</li>
<li><strong>The Partner</strong></li>
</ul>
<h2>Review Ethics Statement</h2>
<h3>Paid Reviews and Sponsorships</h3>
<p>
Laser Everything is a viewer-supported channel and is dedicated to remaining unbiased and
neutral when presenting information and results uncovered during testing and review procedures.
When publishing reviews, Laser Everything does not perform marketing services in exchange for
equipment, goods, software, services, or monetary compensation. The Partner agrees to send
equipment, goods, software, or services for the sole purpose of review and exposure.
Sponsorships are rare; when Laser Everything cooperates with a sponsor, the sponsorship is
always clearly disclosed and will never be presented in a way that suggests the content is not sponsored.
</p>
<h3>Editing and Censorship</h3>
<p>
Laser Everything will provide its viewership with the unedited and uncensored opinion formed
after having spent time with the equipment, goods, software, or services. The Partner
understands and acknowledges that at no time will a preview copy of content or material be
provided, and revisions will not be made to published or unpublished content.
</p>
<h2>Partner Acknowledgements</h2>
<h3>Expectation of Sales</h3>
<p>
The Partner provides all equipment, goods, software, or services without the expectation that
viewership will proceed with any purchase. Laser Everything makes no guarantees that its
viewership will make any purchase and commits to no additional advertising or promotion on
behalf of the Partner.
</p>
<h3>Date of Publication</h3>
<p>
Laser Everything makes no claims or guarantees that content will be published within a specific
time period. The Partner acknowledges the publication date of all content is at the sole
discretion of Laser Everything.
</p>
<h3>Decision to Publish</h3>
<p>
Laser Everything makes no claims or guarantees that content will be published featuring any
provided equipment, goods, software, or services. The Partner acknowledges the decision to
publish any and all content is at the sole discretion of Laser Everything.
</p>
<h3>Liability Release</h3>
<p>
The Partner acknowledges and understands that all content—published or unpublished—by Laser
Everything is presented solely as the opinion of the reviewer. The Partner releases and forever
discharges Laser Everything, its owners, directors, officers, employees, agents, assigns, legal
representatives, and successors from all manner of actions, causes of action, debts, accounts,
bonds, contracts, claims, and demands for or by reason of any damage, loss, or injury to person
or property which has or may be sustained as a consequence of any content produced that has
been published or remains unpublished.
</p>
<h2>Content Release</h2>
<div class="callout legal">
<p><strong>The Partner may choose or is currently engaged</strong> in the business of creating media, which includes (but is not limited to):</p>
<ul>
<li>Filming, film/video editing, and film/video production</li>
<li>Photography, photo editing, and photo production</li>
<li>Digital photography, digital photo editing, and digital photo production</li>
<li>Documentary production and editing</li>
<li>Sound recordings; sound manipulation and music productions</li>
<li>Television production</li>
<li>Web design and production</li>
</ul>
<p>
The Partner consents to its equipment, goods, software, or services being a subject of Laser
Everything in media, and will allow Laser Everything to capture images and sound recordings
for use in media.
</p>
</div>
<h2>Whereby: Laser Everything is Released of Liability</h2>
<p>
For good and valuable consideration herein acknowledged as received, the Partner releases Laser
Everything and assigns permission to license all images and sound recordings and to use them in
any media for any purpose, which may include—among others—advertising, promotion, marketing,
and packaging for any product or service. The Partner agrees that any images and sound
recordings may be combined with other images, text, and graphics, and may be cropped, altered,
and modified.
</p>
<h2>Laser Everything LLC Retains All Rights</h2>
<p>
The Partner agrees that Laser Everything has all rights to images and sound recordings, for
perpetuity. The Partner acknowledges and agrees that Laser Everything is not liable for any
further consideration, accounting, or claim for any reason.
</p>
<h2>Duration of Agreement</h2>
<p>
The Partner acknowledges and agrees that this Agreement is binding on all heirs and assigns.
The Partner acknowledges and agrees that this Agreement is irrevocable, worldwide, and
perpetual. This Agreement contains the entire agreement between the parties to this release,
and the terms of this Agreement are contractual and not a mere recital. This Agreement will be
construed in accordance with and governed by the laws of the State of New York.
</p>
<div class="sig">
<div><strong>Laser Everything</strong></div>
<div>ALEXANDER SELLITE</div>
<div>Member</div>
<div>Laser Everything LLC</div>
</div>
</article>
</main>
</div>
<script>
(function () {
const link = document.getElementById('backLink');
link.addEventListener('click', function (e) {
try {
const sameOrigin = document.referrer && new URL(document.referrer).origin === location.origin;
if (sameOrigin && history.length > 1) { e.preventDefault(); history.back(); }
} catch(_) {}
});
})();
</script>
</body>
</html>

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
icons/bgrm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/castopod.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
icons/db-c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
icons/db-cg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
icons/db-f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
icons/db-l.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
icons/db-m.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/db-mc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
icons/db-p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
icons/db-u.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
icons/db.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
icons/deck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/dpi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
icons/ergo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
icons/ethics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
icons/facebook-g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
icons/facebook-p.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/fediverse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
icons/fs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
icons/instagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
icons/interval.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

BIN
icons/kofi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
icons/lemmy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/liberapay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
icons/lma.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
icons/makearmy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
icons/mastodon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/matrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/news.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
icons/nextcloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

BIN
icons/patreon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/peertube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
icons/picsur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/pixelfed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
icons/privatebin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
icons/support.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
icons/tiktok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
icons/toolkit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
icons/training.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
icons/youtube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

295
index.html Normal file
View file

@ -0,0 +1,295 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Laser Everything</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header class="site-header" id="siteHeader" role="banner">
<a class="brand" href="/">LASER EVERYTHING</a>
</header>
<main id="grid" class="grid" aria-label="Masonry menu"></main>
<script>
/* ======================================================
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)
====================================================== */
(async () => {
/* ---------- tiny seeded RNG (deterministic) ---------- */
function xmur3(str){
let h = 1779033703 ^ str.length;
for (let i=0;i<str.length;i++){
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = (h<<13) | (h>>>19);
}
return function(){
h = Math.imul(h ^ (h>>>16), 2246822507);
h = Math.imul(h ^ (h>>>13), 3266489909);
return (h ^= h>>>16) >>> 0;
};
}
function mulberry32(a){
return function(){
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ (t>>>15), t | 1);
t ^= t + Math.imul(t ^ (t>>>7), t | 61);
return ((t ^ (t>>>14)) >>> 0) / 4294967296;
};
}
const rngFrom = (seedStr) => mulberry32(xmur3(seedStr)());
/* ---------- load config ---------- */
function resolveConfigPath(){
const q = new URLSearchParams(location.search).get('config');
let p = q || '/configs/index.json';
if (!/^https?:\/\//i.test(p) && !p.startsWith('/')) p = '/configs/'+p;
if (!/\.json(\?|$)/i.test(p)) p += '.json';
const u = new URL(p, location.origin);
if (u.origin !== location.origin) throw new Error('Cross-origin config not allowed');
return u.pathname + u.search;
}
// Default config (simple & clear). Your JSON can override any/all.
let cfg = {
brand: "LASER EVERYTHING",
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
reveal: { durationMs: 2600, revertDelayMs: 700 },
hint: {
enabled: true,
rows: 5, // number of rows
textCase: "upper", // 'upper' | 'lower' | 'none'
fontPx: 16, // base size in px
opacity: 0.12,
angleDeg: -12, // rotation of the hint plane
rowGapPx: 8, // vertical gap between rows
spacing: "\u00A0\u00A0\u00A0",// between repeats (use NBSP or your string)
planeScale: 3, // plane size vs tile (3 = 300% both axes)
offsetAmpPct: 35, // max per-row horizontal offset (% of tile width)
featherPct: 0 // 0..10 soft mask on extreme left/right (optional)
},
tiles: []
};
let CONFIG_URL = '/configs/index.json';
try { CONFIG_URL = resolveConfigPath(); } catch {}
try {
const r = await fetch(CONFIG_URL, { cache:'no-store' });
if (r.ok) Object.assign(cfg, await r.json());
} catch {}
/* ---------- apply brand + CSS vars ---------- */
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.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
// Hint CSS vars
const H = cfg.hint || {};
const root = document.documentElement;
root.style.setProperty('--hint-opacity', String(H.opacity ?? 0.12));
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-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
root.style.setProperty('--hint-plane', (H.planeScale!=null ? +H.planeScale : 3));
const grid = document.getElementById('grid');
/* ---------- helpers ---------- */
function parseSpan(v){
if (typeof v==='number') return {w:v,h:v};
if (typeof v==='string'){ const m=v.toLowerCase().match(/^(\d+)x(\d+)$/); if (m) return {w:+m[1], h:+m[2]}; }
if (v && typeof v==='object') return {w:Math.max(1,+v.w||1), h:Math.max(1,+v.h||1)};
return {w:1,h:1};
}
function titleForHint(title){
if (!title) return '';
const mode = (cfg.hint?.textCase ?? 'upper').toLowerCase();
if (mode === 'upper') return title.toUpperCase();
if (mode === 'lower') return title.toLowerCase();
return title;
}
/* ---------- build tiles ---------- */
const frag = document.createDocumentFragment();
const tiles = [];
(cfg.tiles||[]).forEach((t,i)=>{
const a = document.createElement('a');
a.className='tile';
a.href = t.href || '#';
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
if (t.color) a.style.setProperty('--color', t.color);
if (t.image){
const src=(t.image.startsWith('/')||t.image.startsWith('http')) ? t.image : ('/images/'+t.image);
a.style.backgroundImage='url("'+src+'")';
a.style.backgroundSize=t.bgFit||'cover';
a.style.backgroundRepeat='no-repeat';
a.style.backgroundPosition=t.bgPos||'center';
}
// Wipe cover
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
// ===== Hint layer (novel approach) =====
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
const hintLayer = document.createElement('div');
hintLayer.className = 'hint-layer';
if ((cfg.hint?.featherPct|0) > 0){
hintLayer.style.setProperty('--hint-feather', (cfg.hint.featherPct|0) + '%');
}
const stack = document.createElement('div');
stack.className = 'hint-stack';
stack.style.setProperty('--plane-scale', String(cfg.hint?.planeScale ?? 3));
hintLayer.appendChild(stack);
// Prepare deterministic RNG per tile
const seed = 'row|' + (t.title || ('tile'+i));
const rnd = rngFrom(seed);
// Compose very long content (no measuring).
const baseTitle = titleForHint(t.hintTitle || t.title || '');
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
const chunk = (baseTitle + spacing);
const repeats = 240; // big enough that it ALWAYS bleeds past edges
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);
for (let r=0; r<rows; r++){
const row = document.createElement('div');
row.className = 'hint-row';
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);
row.dataset.offsetScale = String(offs); // fraction of tile width
stack.appendChild(row);
}
a.appendChild(hintLayer);
}
// Icon layer
const iconL=document.createElement('div'); iconL.className='layer icon-layer';
if (t.icon){
if (/\.(png|svg|jpg|jpeg|webp|gif)$/i.test(t.icon)) {
const img=document.createElement('img'); img.className='icon'; img.alt=''; img.src=t.icon; iconL.appendChild(img);
} else {
const span=document.createElement('span'); span.className='icon icon-text'; span.textContent=String(t.icon); iconL.appendChild(span);
}
}
a.appendChild(iconL);
// Text layer
const textL=document.createElement('div'); textL.className='layer text-layer';
const wrap=document.createElement('div');
const k=document.createElement('div'); k.className='kicker'; k.textContent=t.kicker||''; if(!k.textContent) k.style.display='none'; wrap.appendChild(k);
const h2=document.createElement('h2'); h2.className='title'; h2.textContent=t.title||('Tile '+(i+1)); wrap.appendChild(h2);
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);
// Sizing
const span = parseSpan(t.size || 1);
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
frag.appendChild(a);
tiles.push(a);
});
grid.textContent = '';
grid.appendChild(frag);
/* ---------- layout (header-aware, deterministic wipe) ---------- */
function layout(){
const W = innerWidth;
const H = innerHeight;
const V = visualViewport;
const vh = (V && V.height) ? V.height : H;
const header = document.getElementById('siteHeader');
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
const S = tiles.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
const A = W / (HH || 1);
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
let rows = Math.max(1, Math.ceil(S / cols));
const unitFor = (c, r) => Math.max(1, Math.min(
Math.floor((W - gap*(c+1)) / c),
Math.floor((HH - gap*(r+1)) / r)
));
let best = { cols, rows, unit: unitFor(cols, rows) };
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
const r = Math.max(1, Math.ceil(S / c));
const u = unitFor(c, r);
if (u > best.unit || (u === best.unit && r < best.rows)) best = { cols:c, rows:r, unit:u };
}
grid.style.gap = gap + 'px';
grid.style.padding = gap + 'px';
if (fit === 'stretch'){
const unitW = (W - gap * (best.cols + 1)) / best.cols;
const unitH = (HH - gap * (best.rows + 1)) / best.rows;
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${unitW}px)`;
grid.style.gridAutoRows = `${unitH}px`;
} else {
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${best.unit}px)`;
grid.style.gridAutoRows = `${best.unit}px`;
}
tiles.forEach(n=>{
n.style.gridColumn = `span ${n.dataset.w}`;
n.style.gridRow = `span ${n.dataset.h}`;
// Resolve deterministic wipe dir (seeded by title)
const title = n.querySelector('.title')?.textContent || '';
const R = rngFrom('wipe|'+title);
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
const [dx,dy] = dirs[Math.floor(R()*dirs.length)];
n.style.setProperty('--dx', (dx*140)+'%');
n.style.setProperty('--dy', (dy*140)+'%');
// Update row pixel offsets based on current tile width
const tw = n.getBoundingClientRect().width;
n.querySelectorAll('.hint-row').forEach(row=>{
const scale = parseFloat(row.dataset.offsetScale || '0'); // [-1..1] * amp%
const px = scale * tw; // px offset
row.style.setProperty('--row-offset-px', px + 'px');
});
});
}
layout();
const resched = () => { clearTimeout(layout._t); layout._t=setTimeout(layout, 50); };
addEventListener('resize', resched);
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
// JS-assisted reveal class only
tiles.forEach(tile=>{
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
tile.addEventListener('focusout', ()=>tile.classList.remove('js-reveal'));
});
})();
</script>
</body>
</html>

295
social.html Normal file
View file

@ -0,0 +1,295 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Laser Everything</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header class="site-header" id="siteHeader" role="banner">
<a class="brand" href="/">LASER EVERYTHING</a>
</header>
<main id="grid" class="grid" aria-label="Masonry menu"></main>
<script>
/* ======================================================
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)
====================================================== */
(async () => {
/* ---------- tiny seeded RNG (deterministic) ---------- */
function xmur3(str){
let h = 1779033703 ^ str.length;
for (let i=0;i<str.length;i++){
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = (h<<13) | (h>>>19);
}
return function(){
h = Math.imul(h ^ (h>>>16), 2246822507);
h = Math.imul(h ^ (h>>>13), 3266489909);
return (h ^= h>>>16) >>> 0;
};
}
function mulberry32(a){
return function(){
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ (t>>>15), t | 1);
t ^= t + Math.imul(t ^ (t>>>7), t | 61);
return ((t ^ (t>>>14)) >>> 0) / 4294967296;
};
}
const rngFrom = (seedStr) => mulberry32(xmur3(seedStr)());
/* ---------- load config ---------- */
function resolveConfigPath(){
const q = new URLSearchParams(location.search).get('config');
let p = q || '/configs/social.json';
if (!/^https?:\/\//i.test(p) && !p.startsWith('/')) p = '/configs/'+p;
if (!/\.json(\?|$)/i.test(p)) p += '.json';
const u = new URL(p, location.origin);
if (u.origin !== location.origin) throw new Error('Cross-origin config not allowed');
return u.pathname + u.search;
}
// Default config (simple & clear). Your JSON can override any/all.
let cfg = {
brand: "LASER EVERYTHING",
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
reveal: { durationMs: 2600, revertDelayMs: 700 },
hint: {
enabled: true,
rows: 5, // number of rows
textCase: "upper", // 'upper' | 'lower' | 'none'
fontPx: 16, // base size in px
opacity: 0.12,
angleDeg: -12, // rotation of the hint plane
rowGapPx: 8, // vertical gap between rows
spacing: "\u00A0\u00A0\u00A0",// between repeats (use NBSP or your string)
planeScale: 3, // plane size vs tile (3 = 300% both axes)
offsetAmpPct: 35, // max per-row horizontal offset (% of tile width)
featherPct: 0 // 0..10 soft mask on extreme left/right (optional)
},
tiles: []
};
let CONFIG_URL = '/configs/index.json';
try { CONFIG_URL = resolveConfigPath(); } catch {}
try {
const r = await fetch(CONFIG_URL, { cache:'no-store' });
if (r.ok) Object.assign(cfg, await r.json());
} catch {}
/* ---------- apply brand + CSS vars ---------- */
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.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
// Hint CSS vars
const H = cfg.hint || {};
const root = document.documentElement;
root.style.setProperty('--hint-opacity', String(H.opacity ?? 0.12));
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-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
root.style.setProperty('--hint-plane', (H.planeScale!=null ? +H.planeScale : 3));
const grid = document.getElementById('grid');
/* ---------- helpers ---------- */
function parseSpan(v){
if (typeof v==='number') return {w:v,h:v};
if (typeof v==='string'){ const m=v.toLowerCase().match(/^(\d+)x(\d+)$/); if (m) return {w:+m[1], h:+m[2]}; }
if (v && typeof v==='object') return {w:Math.max(1,+v.w||1), h:Math.max(1,+v.h||1)};
return {w:1,h:1};
}
function titleForHint(title){
if (!title) return '';
const mode = (cfg.hint?.textCase ?? 'upper').toLowerCase();
if (mode === 'upper') return title.toUpperCase();
if (mode === 'lower') return title.toLowerCase();
return title;
}
/* ---------- build tiles ---------- */
const frag = document.createDocumentFragment();
const tiles = [];
(cfg.tiles||[]).forEach((t,i)=>{
const a = document.createElement('a');
a.className='tile';
a.href = t.href || '#';
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
if (t.color) a.style.setProperty('--color', t.color);
if (t.image){
const src=(t.image.startsWith('/')||t.image.startsWith('http')) ? t.image : ('/images/'+t.image);
a.style.backgroundImage='url("'+src+'")';
a.style.backgroundSize=t.bgFit||'cover';
a.style.backgroundRepeat='no-repeat';
a.style.backgroundPosition=t.bgPos||'center';
}
// Wipe cover
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
// ===== Hint layer (novel approach) =====
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
const hintLayer = document.createElement('div');
hintLayer.className = 'hint-layer';
if ((cfg.hint?.featherPct|0) > 0){
hintLayer.style.setProperty('--hint-feather', (cfg.hint.featherPct|0) + '%');
}
const stack = document.createElement('div');
stack.className = 'hint-stack';
stack.style.setProperty('--plane-scale', String(cfg.hint?.planeScale ?? 3));
hintLayer.appendChild(stack);
// Prepare deterministic RNG per tile
const seed = 'row|' + (t.title || ('tile'+i));
const rnd = rngFrom(seed);
// Compose very long content (no measuring).
const baseTitle = titleForHint(t.hintTitle || t.title || '');
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
const chunk = (baseTitle + spacing);
const repeats = 240; // big enough that it ALWAYS bleeds past edges
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);
for (let r=0; r<rows; r++){
const row = document.createElement('div');
row.className = 'hint-row';
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);
row.dataset.offsetScale = String(offs); // fraction of tile width
stack.appendChild(row);
}
a.appendChild(hintLayer);
}
// Icon layer
const iconL=document.createElement('div'); iconL.className='layer icon-layer';
if (t.icon){
if (/\.(png|svg|jpg|jpeg|webp|gif)$/i.test(t.icon)) {
const img=document.createElement('img'); img.className='icon'; img.alt=''; img.src=t.icon; iconL.appendChild(img);
} else {
const span=document.createElement('span'); span.className='icon icon-text'; span.textContent=String(t.icon); iconL.appendChild(span);
}
}
a.appendChild(iconL);
// Text layer
const textL=document.createElement('div'); textL.className='layer text-layer';
const wrap=document.createElement('div');
const k=document.createElement('div'); k.className='kicker'; k.textContent=t.kicker||''; if(!k.textContent) k.style.display='none'; wrap.appendChild(k);
const h2=document.createElement('h2'); h2.className='title'; h2.textContent=t.title||('Tile '+(i+1)); wrap.appendChild(h2);
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);
// Sizing
const span = parseSpan(t.size || 1);
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
frag.appendChild(a);
tiles.push(a);
});
grid.textContent = '';
grid.appendChild(frag);
/* ---------- layout (header-aware, deterministic wipe) ---------- */
function layout(){
const W = innerWidth;
const H = innerHeight;
const V = visualViewport;
const vh = (V && V.height) ? V.height : H;
const header = document.getElementById('siteHeader');
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
const S = tiles.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
const A = W / (HH || 1);
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
let rows = Math.max(1, Math.ceil(S / cols));
const unitFor = (c, r) => Math.max(1, Math.min(
Math.floor((W - gap*(c+1)) / c),
Math.floor((HH - gap*(r+1)) / r)
));
let best = { cols, rows, unit: unitFor(cols, rows) };
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
const r = Math.max(1, Math.ceil(S / c));
const u = unitFor(c, r);
if (u > best.unit || (u === best.unit && r < best.rows)) best = { cols:c, rows:r, unit:u };
}
grid.style.gap = gap + 'px';
grid.style.padding = gap + 'px';
if (fit === 'stretch'){
const unitW = (W - gap * (best.cols + 1)) / best.cols;
const unitH = (HH - gap * (best.rows + 1)) / best.rows;
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${unitW}px)`;
grid.style.gridAutoRows = `${unitH}px`;
} else {
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${best.unit}px)`;
grid.style.gridAutoRows = `${best.unit}px`;
}
tiles.forEach(n=>{
n.style.gridColumn = `span ${n.dataset.w}`;
n.style.gridRow = `span ${n.dataset.h}`;
// Resolve deterministic wipe dir (seeded by title)
const title = n.querySelector('.title')?.textContent || '';
const R = rngFrom('wipe|'+title);
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
const [dx,dy] = dirs[Math.floor(R()*dirs.length)];
n.style.setProperty('--dx', (dx*140)+'%');
n.style.setProperty('--dy', (dy*140)+'%');
// Update row pixel offsets based on current tile width
const tw = n.getBoundingClientRect().width;
n.querySelectorAll('.hint-row').forEach(row=>{
const scale = parseFloat(row.dataset.offsetScale || '0'); // [-1..1] * amp%
const px = scale * tw; // px offset
row.style.setProperty('--row-offset-px', px + 'px');
});
});
}
layout();
const resched = () => { clearTimeout(layout._t); layout._t=setTimeout(layout, 50); };
addEventListener('resize', resched);
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
// JS-assisted reveal class only
tiles.forEach(tile=>{
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
tile.addEventListener('focusout', ()=>tile.classList.remove('js-reveal'));
});
})();
</script>
</body>
</html>

193
styles.css Normal file
View file

@ -0,0 +1,193 @@
:root{
--bg:#121212;
--fg:#eceff3;
--gap:12px;
--reveal-ms:2600ms;
--header-h:56px;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
/* Hint knobs (wired from config.hint) */
--hint-opacity:.12;
--hint-angle:-12deg;
--hint-size:16px;
--hint-row-gap:8px;
--hint-plane:3;
--hint-feather:0%;
}
/* ===== Base ===== */
*{ box-sizing:border-box }
html,body{ height:100% }
body{
margin:0; color:var(--fg); background:var(--bg);
font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,Arial;
overflow:hidden; /* tiles view */
}
a{ color:inherit; text-decoration:none }
/* ===== Header ===== */
.site-header{
position:fixed; left:0; right:0; top:0;
height:calc(var(--header-h) + var(--safe-top));
display:flex; align-items:center; justify-content:center;
padding:0 clamp(12px,3vw,24px);
background:rgba(0,0,0,.55); backdrop-filter:blur(6px);
border-bottom:1px solid rgba(255,255,255,.10);
z-index:2000;
}
.site-header .brand{
font-weight:900; letter-spacing:.22em; text-transform:uppercase;
font-size:clamp(12px,1.4vw,14px); opacity:.95; text-decoration:none;
}
/* ===== Grid ===== */
.grid{
position:fixed; inset:0;
top:calc(var(--header-h) + var(--safe-top));
display:grid; grid-auto-flow:dense;
grid-template-columns:repeat(12, 80px);
grid-auto-rows:80px;
gap:var(--gap); padding:var(--gap);
}
/* ===== Tiles ===== */
.tile{
position:relative; display:block; overflow:hidden;
background:var(--color,#1f2937);
border:1px solid rgba(255,255,255,.08);
transition:opacity .55s ease, transform .55s ease;
}
.cover{
position:absolute; inset:0; z-index:2; background:#242424;
transform:translate(0,0);
transition:transform var(--reveal-ms) cubic-bezier(.22,.8,.18,1);
pointer-events:none; will-change:transform;
}
.layer,.cover{
position:absolute; inset:0; display:grid; place-items:center; justify-items:center;
text-align:center; padding:clamp(12px,3vh,28px);
transition:opacity .55s ease, transform .55s ease; pointer-events:none;
}
.text-layer{ z-index:1; opacity:0; transform:translateY(8px) }
.icon-layer{ z-index:4; opacity:1 }
.icon{ width:clamp(36px,9vh,110px); height:auto; object-fit:contain }
.icon.icon-text{ width:auto; font-size:clamp(24px,10vh,120px); line-height:1 }
.kicker{ font-weight:900; letter-spacing:.22em; text-transform:uppercase; font-size:12px; opacity:.95; margin:0 0 .25em }
.title{ margin:0; font-weight:1000; letter-spacing:.2px; font-size:clamp(16px,2.2vw,28px); line-height:1.1 }
.desc{ margin:.45em 0 0; opacity:.92; font-size:14px; max-width:min(72ch,90%) }
.text-layer>div{ display:flex; flex-direction:column; align-items:center }
/* Reveal states */
.tile:hover .cover,
.tile:focus-visible .cover,
.tile.js-reveal .cover{ transform:translate(var(--dx),var(--dy)) }
.tile:hover .icon-layer,
.tile:focus-visible .icon-layer,
.tile.js-reveal .icon-layer{ opacity:0; transform:scale(.965) }
.tile:hover .text-layer,
.tile:focus-visible .text-layer,
.tile.js-reveal .text-layer{ opacity:1; transform:none }
@media (prefers-reduced-motion:reduce){
.tile,.cover,.layer{ transition:none }
}
/* ===== Hint layer (novel approach) =====
* A huge plane rotated & centered. Rows are long lines of text.
* The tile clips the plane, so text can be sliced mid-word at edges. */
.hint-layer{
position:absolute; inset:0; z-index:3; pointer-events:none;
display:block; opacity:var(--hint-opacity);
/* optional soft fade at extreme sides */
-webkit-mask-image:linear-gradient(to right,
rgba(0,0,0,0) var(--hint-feather),
#000 calc(var(--hint-feather) + 1px),
#000 calc(100% - var(--hint-feather) - 1px),
rgba(0,0,0,0) calc(100% - var(--hint-feather)));
mask-image:linear-gradient(to right,
rgba(0,0,0,0) var(--hint-feather),
#000 calc(var(--hint-feather) + 1px),
#000 calc(100% - var(--hint-feather) - 1px),
rgba(0,0,0,0) calc(100% - var(--hint-feather)));
}
.hint-stack{
position:absolute; left:50%; top:50%;
transform:translate(-50%,-50%) rotate(var(--hint-angle));
width:calc(var(--hint-plane) * 100%);
height:calc(var(--hint-plane) * 100%);
display:flex; flex-direction:column; justify-content:center; align-items:center;
gap:var(--hint-row-gap);
overflow:hidden; /* clip rows to plane; tile clips plane */
}
.hint-row{
position:relative; display:block;
/* make the line VERY long; no measuring required */
white-space:nowrap; letter-spacing:.22em; line-height:1;
font-weight:900; text-transform:uppercase; font-size:var(--hint-size);
transform:translateX(var(--row-offset-px, 0px));
/* the content string itself is repeated in JS so it actually fills */
}
/* Hide hint during reveal for the “flip” effect */
.tile:hover .hint-layer,
.tile:focus-visible .hint-layer,
.tile.js-reveal .hint-layer{ opacity:0 }
/* ===== Static pages (unchanged from prior work) ===== */
body.page{
min-height:100vh; margin:0; color:var(--fg); background:var(--bg);
overflow-y:auto; overflow-x:hidden;
}
.page-wrap{
box-sizing:border-box; min-height:100svh;
padding:var(--gap);
padding-top:calc(var(--gap) + var(--header-h) + var(--safe-top));
padding-bottom:calc(var(--gap) + var(--safe-bottom));
display:grid; grid-template-rows:auto 1fr; gap:var(--gap);
}
.page-header{ display:flex; align-items:center; justify-content:space-between; gap:var(--gap) }
.back-btn,.btn{
display:inline-flex; align-items:center; gap:.6rem;
border-radius:999px; padding:.6rem .9rem;
border:1px solid rgba(255,255,255,0.10);
background:rgba(255,255,255,0.04);
color:inherit; text-decoration:none; outline:none;
transition:background 300ms, transform 160ms;
}
.btn.primary{ background:rgba(255,255,255,0.08) }
.back-btn:hover,.btn:hover{ background:rgba(255,255,255,0.07); transform:translateY(-1px) }
.back-btn:focus-visible,.btn:focus-visible{ box-shadow:0 0 0 3px rgba(154,168,255,.45) }
.back-btn svg{ width:18px; height:18px; flex:0 0 auto; opacity:.95 }
.page-main{ display:grid; place-items:start center }
.article{
width:min(90ch,100%); line-height:1.6;
background:rgba(255,255,255,0.04);
border:1px solid rgba(255,255,255,0.10);
border-radius:16px;
padding:clamp(16px,3vw,28px);
}
.article h1{ margin:0 0 .35em; font-size:clamp(22px,3.2vw,36px); letter-spacing:.2px; text-align:center }
.subtitle{ margin:0 0 1.1em; opacity:.85; text-align:center; font-size:clamp(14px,1.6vw,16px) }
.cta-row{ display:flex; flex-wrap:wrap; gap:.6rem; justify-content:center; margin:0 0 1.25em }
.callout{ margin:1.2em 0; padding:1rem; border:1px solid rgba(255,255,255,0.10); border-radius:12px; background:rgba(255,255,255,.03) }
.callout.warn{ border-color:rgba(255,99,99,.5); background:rgba(255,0,0,.06) }
.callout h3{ margin:.2em 0 .6em; font-size:1rem; letter-spacing:.08em; text-transform:uppercase; opacity:.9 }
.article h2{ margin:1.6em 0 .6em; font-size:clamp(18px,2.4vw,24px) }
.article h3{ margin:1.2em 0 .4em; font-size:clamp(16px,2vw,20px) }
.article p{ margin:.8em 0 }
.article ul,.article ol{ margin:.8em 0 .8em 1.2em }
.article a{ color:inherit; text-decoration:underline; text-underline-offset:2px }
.legal{ border-color:rgba(255,255,255,.18) }
.sig{ margin-top:1.5em; padding-top:1em; border-top:1px solid rgba(255,255,255,0.10); display:grid; gap:.25rem }
.party-list{ display:grid; gap:.35rem; margin:.6rem 0 1rem; padding-left:1.2rem }
/* ===== Mobile ===== */
@media (max-width:480px){
:root{ --gap:8px; --header-h:48px }
.kicker{ font-size:11px }
.title{ font-size:clamp(16px,5.2vw,22px) }
.desc{ font-size:13px }
.icon{ width:clamp(28px,11vh,90px) }
.back-btn,.btn{ padding:.55rem .85rem }
}

295
support.html Normal file
View file

@ -0,0 +1,295 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Laser Everything</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header class="site-header" id="siteHeader" role="banner">
<a class="brand" href="/">LASER EVERYTHING</a>
</header>
<main id="grid" class="grid" aria-label="Masonry menu"></main>
<script>
/* ======================================================
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)
====================================================== */
(async () => {
/* ---------- tiny seeded RNG (deterministic) ---------- */
function xmur3(str){
let h = 1779033703 ^ str.length;
for (let i=0;i<str.length;i++){
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = (h<<13) | (h>>>19);
}
return function(){
h = Math.imul(h ^ (h>>>16), 2246822507);
h = Math.imul(h ^ (h>>>13), 3266489909);
return (h ^= h>>>16) >>> 0;
};
}
function mulberry32(a){
return function(){
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ (t>>>15), t | 1);
t ^= t + Math.imul(t ^ (t>>>7), t | 61);
return ((t ^ (t>>>14)) >>> 0) / 4294967296;
};
}
const rngFrom = (seedStr) => mulberry32(xmur3(seedStr)());
/* ---------- load config ---------- */
function resolveConfigPath(){
const q = new URLSearchParams(location.search).get('config');
let p = q || '/configs/support.json';
if (!/^https?:\/\//i.test(p) && !p.startsWith('/')) p = '/configs/'+p;
if (!/\.json(\?|$)/i.test(p)) p += '.json';
const u = new URL(p, location.origin);
if (u.origin !== location.origin) throw new Error('Cross-origin config not allowed');
return u.pathname + u.search;
}
// Default config (simple & clear). Your JSON can override any/all.
let cfg = {
brand: "LASER EVERYTHING",
grid: { gap: 12, fit: "stretch" }, // fit: 'stretch' | 'fitCells'
reveal: { durationMs: 2600, revertDelayMs: 700 },
hint: {
enabled: true,
rows: 5, // number of rows
textCase: "upper", // 'upper' | 'lower' | 'none'
fontPx: 16, // base size in px
opacity: 0.12,
angleDeg: -12, // rotation of the hint plane
rowGapPx: 8, // vertical gap between rows
spacing: "\u00A0\u00A0\u00A0",// between repeats (use NBSP or your string)
planeScale: 3, // plane size vs tile (3 = 300% both axes)
offsetAmpPct: 35, // max per-row horizontal offset (% of tile width)
featherPct: 0 // 0..10 soft mask on extreme left/right (optional)
},
tiles: []
};
let CONFIG_URL = '/configs/index.json';
try { CONFIG_URL = resolveConfigPath(); } catch {}
try {
const r = await fetch(CONFIG_URL, { cache:'no-store' });
if (r.ok) Object.assign(cfg, await r.json());
} catch {}
/* ---------- apply brand + CSS vars ---------- */
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.reveal?.durationMs != null) document.documentElement.style.setProperty('--reveal-ms', (cfg.reveal.durationMs|0)+'ms');
// Hint CSS vars
const H = cfg.hint || {};
const root = document.documentElement;
root.style.setProperty('--hint-opacity', String(H.opacity ?? 0.12));
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-row-gap', (H.rowGapPx!=null ? (H.rowGapPx|0)+'px' : '8px'));
root.style.setProperty('--hint-plane', (H.planeScale!=null ? +H.planeScale : 3));
const grid = document.getElementById('grid');
/* ---------- helpers ---------- */
function parseSpan(v){
if (typeof v==='number') return {w:v,h:v};
if (typeof v==='string'){ const m=v.toLowerCase().match(/^(\d+)x(\d+)$/); if (m) return {w:+m[1], h:+m[2]}; }
if (v && typeof v==='object') return {w:Math.max(1,+v.w||1), h:Math.max(1,+v.h||1)};
return {w:1,h:1};
}
function titleForHint(title){
if (!title) return '';
const mode = (cfg.hint?.textCase ?? 'upper').toLowerCase();
if (mode === 'upper') return title.toUpperCase();
if (mode === 'lower') return title.toLowerCase();
return title;
}
/* ---------- build tiles ---------- */
const frag = document.createDocumentFragment();
const tiles = [];
(cfg.tiles||[]).forEach((t,i)=>{
const a = document.createElement('a');
a.className='tile';
a.href = t.href || '#';
a.setAttribute('aria-label', t.title || ('Tile ' + (i+1)));
if (t.color) a.style.setProperty('--color', t.color);
if (t.image){
const src=(t.image.startsWith('/')||t.image.startsWith('http')) ? t.image : ('/images/'+t.image);
a.style.backgroundImage='url("'+src+'")';
a.style.backgroundSize=t.bgFit||'cover';
a.style.backgroundRepeat='no-repeat';
a.style.backgroundPosition=t.bgPos||'center';
}
// Wipe cover
const cover = document.createElement('div'); cover.className='cover'; a.appendChild(cover);
// ===== Hint layer (novel approach) =====
if ((cfg.hint?.enabled!==false) && (t.hintTitle || t.title)){
const hintLayer = document.createElement('div');
hintLayer.className = 'hint-layer';
if ((cfg.hint?.featherPct|0) > 0){
hintLayer.style.setProperty('--hint-feather', (cfg.hint.featherPct|0) + '%');
}
const stack = document.createElement('div');
stack.className = 'hint-stack';
stack.style.setProperty('--plane-scale', String(cfg.hint?.planeScale ?? 3));
hintLayer.appendChild(stack);
// Prepare deterministic RNG per tile
const seed = 'row|' + (t.title || ('tile'+i));
const rnd = rngFrom(seed);
// Compose very long content (no measuring).
const baseTitle = titleForHint(t.hintTitle || t.title || '');
const spacing = (cfg.hint?.spacing ?? '\u00A0\u00A0\u00A0');
const chunk = (baseTitle + spacing);
const repeats = 240; // big enough that it ALWAYS bleeds past edges
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);
for (let r=0; r<rows; r++){
const row = document.createElement('div');
row.className = 'hint-row';
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);
row.dataset.offsetScale = String(offs); // fraction of tile width
stack.appendChild(row);
}
a.appendChild(hintLayer);
}
// Icon layer
const iconL=document.createElement('div'); iconL.className='layer icon-layer';
if (t.icon){
if (/\.(png|svg|jpg|jpeg|webp|gif)$/i.test(t.icon)) {
const img=document.createElement('img'); img.className='icon'; img.alt=''; img.src=t.icon; iconL.appendChild(img);
} else {
const span=document.createElement('span'); span.className='icon icon-text'; span.textContent=String(t.icon); iconL.appendChild(span);
}
}
a.appendChild(iconL);
// Text layer
const textL=document.createElement('div'); textL.className='layer text-layer';
const wrap=document.createElement('div');
const k=document.createElement('div'); k.className='kicker'; k.textContent=t.kicker||''; if(!k.textContent) k.style.display='none'; wrap.appendChild(k);
const h2=document.createElement('h2'); h2.className='title'; h2.textContent=t.title||('Tile '+(i+1)); wrap.appendChild(h2);
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);
// Sizing
const span = parseSpan(t.size || 1);
a.dataset.w = String(Math.max(1, Math.min(6, span.w)));
a.dataset.h = String(Math.max(1, Math.min(6, span.h)));
frag.appendChild(a);
tiles.push(a);
});
grid.textContent = '';
grid.appendChild(frag);
/* ---------- layout (header-aware, deterministic wipe) ---------- */
function layout(){
const W = innerWidth;
const H = innerHeight;
const V = visualViewport;
const vh = (V && V.height) ? V.height : H;
const header = document.getElementById('siteHeader');
const HH = Math.max(0, vh - (header ? header.getBoundingClientRect().height : 0));
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 0;
const fit = (cfg.grid?.fit || 'stretch').toLowerCase();
const S = tiles.reduce((s, el)=> s + (+el.dataset.w * +el.dataset.h), 0) || 1;
const A = W / (HH || 1);
let cols = Math.max(1, Math.round(Math.sqrt(S * A)));
let rows = Math.max(1, Math.ceil(S / cols));
const unitFor = (c, r) => Math.max(1, Math.min(
Math.floor((W - gap*(c+1)) / c),
Math.floor((HH - gap*(r+1)) / r)
));
let best = { cols, rows, unit: unitFor(cols, rows) };
for (let c=Math.max(1, cols-3); c<=cols+3; c++){
const r = Math.max(1, Math.ceil(S / c));
const u = unitFor(c, r);
if (u > best.unit || (u === best.unit && r < best.rows)) best = { cols:c, rows:r, unit:u };
}
grid.style.gap = gap + 'px';
grid.style.padding = gap + 'px';
if (fit === 'stretch'){
const unitW = (W - gap * (best.cols + 1)) / best.cols;
const unitH = (HH - gap * (best.rows + 1)) / best.rows;
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${unitW}px)`;
grid.style.gridAutoRows = `${unitH}px`;
} else {
grid.style.gridTemplateColumns = `repeat(${best.cols}, ${best.unit}px)`;
grid.style.gridAutoRows = `${best.unit}px`;
}
tiles.forEach(n=>{
n.style.gridColumn = `span ${n.dataset.w}`;
n.style.gridRow = `span ${n.dataset.h}`;
// Resolve deterministic wipe dir (seeded by title)
const title = n.querySelector('.title')?.textContent || '';
const R = rngFrom('wipe|'+title);
const dirs = [[1,0],[-1,0],[0,-1],[0,1],[1,-1],[-1,-1],[-1,1],[1,1]];
const [dx,dy] = dirs[Math.floor(R()*dirs.length)];
n.style.setProperty('--dx', (dx*140)+'%');
n.style.setProperty('--dy', (dy*140)+'%');
// Update row pixel offsets based on current tile width
const tw = n.getBoundingClientRect().width;
n.querySelectorAll('.hint-row').forEach(row=>{
const scale = parseFloat(row.dataset.offsetScale || '0'); // [-1..1] * amp%
const px = scale * tw; // px offset
row.style.setProperty('--row-offset-px', px + 'px');
});
});
}
layout();
const resched = () => { clearTimeout(layout._t); layout._t=setTimeout(layout, 50); };
addEventListener('resize', resched);
if (visualViewport){ visualViewport.addEventListener('resize', resched); visualViewport.addEventListener('scroll', resched); }
// JS-assisted reveal class only
tiles.forEach(tile=>{
tile.addEventListener('mouseenter',()=>tile.classList.add('js-reveal'));
tile.addEventListener('mouseleave',()=>tile.classList.remove('js-reveal'));
tile.addEventListener('focusin', ()=>tile.classList.add('js-reveal'));
tile.addEventListener('focusout', ()=>tile.classList.remove('js-reveal'));
});
})();
</script>
</body>
</html>

98
training.html Normal file
View file

@ -0,0 +1,98 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Laser Engraving Training · LaserEverything</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body class="page">
<div class="page-wrap">
<header class="page-header">
<a class="back-btn" href="/" id="backLink" aria-label="Back to home">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M19 12H5"></path><path d="M12 19l-7-7 7-7"></path>
</svg>
<span>Back to Home</span>
</a>
<div></div>
</header>
<main class="page-main" role="main">
<article class="article">
<h1>Laser Engraving Training</h1>
<p class="subtitle"><strong>ONE</strong> Maker &nbsp; <strong>TWO</strong> Specialists &nbsp; | &nbsp; <strong>ZERO</strong> Issues</p>
<div class="cta-row">
<a class="btn primary" href="mailto:?subject=Book%20Coaching%20Session">Book With Us</a>
<a class="btn" href="mailto:?subject=Book%20Coaching%20Session">Send Us an Email</a>
</div>
<p><em>Send Us an Email</em> with “<strong>Book Coaching Session</strong>” in the subject. Review this page and, in the body, include what youd like to cover and what youre hoping to achieve. Please allow at least 24 hours for a response.</p>
<h2>Were Here to Help</h2>
<p>Dont go it alone. The staff at Laser Everything are here to help you get rolling on your laser journey. Use the following to help estimate how much time youll need to complete your tasks and get your questions answered.</p>
<div class="callout warn">
<h3>Important Note</h3>
<p><strong>We do not cover the following in our coaching sessions:</strong></p>
<ul>
<li>3D or 2.5Dspecific functionality (including software Z-axis and 3D scanners)</li>
<li>3D software troubleshooting and setup (including EzCad&nbsp;3 and LenMark&nbsp;3DS)<br>
<small><em>Does not apply to those with a 3D controller paired with a 2D scanhead, or LightBurn height-map engraving.</em></small>
</li>
<li>Materials that we deem dangerous — see the <a href="/material-safety.html">Material Safety Lookup</a>.</li>
</ul>
<p><strong>Refunds will not be issued</strong> to individuals who ignore this warning. If you have questions, please email us <strong>before</strong> purchase.</p>
</div>
<h2>Come Prepared</h2>
<p>How to make the most out of your coaching sessions allotted time.</p>
<h3>Learn Google Meet</h3>
<p>
Google Meet lets us talk, see each other, and share screens to diagnose issues or teach workflows.
If youve never used it, get familiar ahead of time so we can focus on your goals:
<a href="https://support.google.com/meet/">Start Learning Meet</a>.
</p>
<h3>The Right System</h3>
<p>
Please join the Meet from the device you use to operate your laser so we can see what you see in real time.
Screen sharing requires your permission and is vital to providing optimal support. If thats not possible,
you may join from a phone, though its discouraged.
</p>
<h3>Hardware Ready</h3>
<p>
Have your laser unpacked and connected, with your computer updated (including any pending software updates).
Keep materials on hand (scrap/test stock is great), especially if your session is tied to a specific material or object type.
</p>
<h3>Plan For Your Needs</h3>
<p>
To maximize your time and value, make a list of outcomes you want, plus any questions you have.
Keep your notes handy, and add answers and new questions as we go.
Its the best way to get the most done together—and gives you a reference afterward.
</p>
<h2>Contact Us</h2>
<p>Questions about booking? <a href="mailto:?subject=Book%20Coaching%20Session">Send Us an Email</a>.</p>
</article>
</main>
</div>
<script>
(function () {
const link = document.getElementById('backLink');
link.addEventListener('click', function (e) {
try {
const sameOrigin = document.referrer && new URL(document.referrer).origin === location.origin;
if (sameOrigin && history.length > 1) { e.preventDefault(); history.back(); }
} catch(_) {}
});
})();
</script>
</body>
</html>