Files
boc/glossary/index.html
T
Bernt 1a12fb870b dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study
- New /developers/ page with API docs, SDKs, pricing, use cases
- OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics
- Case study: Glasskiosken i Flatenbadet — complete ROI analysis
- Updated /order/ with recurring missions and frequency dropdown
2026-07-14 15:27:33 +00:00

740 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glossary — Landvex Ecosystem</title>
<meta name="description" content="The complete glossary for quiXzoom, AMOS, and Landvex. Every term defined precisely — from Zoomer to AMOS Prediction Engine.">
<link rel="canonical" href="https://www.quixzoom.com/glossary">
<meta name="robots" content="index, follow">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:title" content="Glossary — Landvex Ecosystem">
<meta property="og:description" content="Every term used across quiXzoom, AMOS, and Landvex. Defined precisely and kept current.">
<meta property="og:url" content="https://www.quixzoom.com/glossary">
<meta property="og:image" content="https://www.quixzoom.com/images/og-glossary.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Glossary — Landvex Ecosystem">
<meta name="twitter:description" content="Every term used across quiXzoom, AMOS, and Landvex. Defined precisely and kept current.">
<meta name="twitter:image" content="https://www.quixzoom.com/images/og-glossary.jpg">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "DefinedTermSet",
"name": "Landvex Ecosystem Glossary",
"description": "Complete glossary of terms used across quiXzoom, AMOS, and Landvex platforms",
"publisher": {
"@type": "Organization",
"name": "Landvex Inc",
"url": "https://www.landvex.com"
}
}
</script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #ffffff;
--bg-off: #f5f5f7;
--bg-card: #ffffff;
--text: #1d1d1f;
--text-secondary: #6e6e73;
--blue: #0066FF;
--blue-light: #e8f0ff;
--green: #00a550;
--green-light: #e6f7ee;
--purple: #7b2fff;
--purple-light: #f0e8ff;
--orange: #e8660a;
--orange-light: #fff0e6;
--pink: #ff3366;
--pink-light: #ffe8f0;
--radius: 18px;
--radius-sm: 8px;
--border: #d2d2d7;
--nav-height: 64px;
--shadow-hover: 0 12px 40px rgba(0,0,0,.10);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #000000;
--bg-off: #1c1c1e;
--bg-card: #1c1c1e;
--text: #f5f5f7;
--text-secondary: #98989f;
--border: #3a3a3c;
--blue-light: rgba(0,102,255,.15);
--green-light: rgba(0,165,80,.15);
--purple-light: rgba(123,47,255,.15);
--orange-light: rgba(232,102,10,.15);
--pink-light: rgba(255,51,102,.15);
--shadow-hover: 0 12px 40px rgba(0,0,0,.5);
}
}
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* ─── NAV ─────────────────────────────────────────────────── */
.nav {
position: sticky;
top: 0;
z-index: 100;
height: var(--nav-height);
background: rgba(255,255,255,.82);
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 16px;
padding: 0 24px;
}
@media (prefers-color-scheme: dark) {
.nav { background: rgba(0,0,0,.82); }
}
.nav-logo {
font-size: 1.25rem;
font-weight: 800;
color: var(--text);
text-decoration: none;
letter-spacing: -.04em;
white-space: nowrap;
flex-shrink: 0;
}
.nav-logo .x { color: var(--blue); }
.nav-divider {
width: 1px;
height: 20px;
background: var(--border);
flex-shrink: 0;
}
.nav-title {
font-size: .9375rem;
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
flex-shrink: 0;
}
.nav-search {
flex: 1;
max-width: 280px;
position: relative;
}
.nav-search input {
width: 100%;
padding: 8px 14px 8px 36px;
border-radius: 20px;
border: 1px solid var(--border);
background: var(--bg-off);
color: var(--text);
font-size: .875rem;
font-family: inherit;
outline: none;
transition: border-color .15s, box-shadow .15s;
}
.nav-search input:focus {
border-color: var(--blue);
box-shadow: 0 0 0 3px rgba(0,102,255,.12);
}
.nav-search-icon {
position: absolute;
left: 11px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
pointer-events: none;
}
.nav-az {
display: flex;
gap: 2px;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.nav-az::-webkit-scrollbar { display: none; }
.nav-az a {
display: inline-block;
min-width: 24px;
padding: 3px 4px;
text-align: center;
font-size: .75rem;
font-weight: 600;
color: var(--text-secondary);
text-decoration: none;
border-radius: 5px;
transition: background .12s, color .12s;
line-height: 1.4;
}
.nav-az a:hover { background: var(--blue-light); color: var(--blue); }
.nav-az a.active { background: var(--blue); color: #fff; }
.nav-az a.empty { opacity: .25; pointer-events: none; }
/* ─── MAIN ────────────────────────────────────────────────── */
main {
max-width: 860px;
margin: 0 auto;
padding: 0 24px 80px;
}
.page-header {
padding: 64px 0 48px;
text-align: center;
}
.page-header h1 {
font-size: clamp(2.5rem, 6vw, 4rem);
font-weight: 800;
letter-spacing: -.04em;
line-height: 1.05;
color: var(--text);
margin-bottom: 16px;
}
.page-header p {
font-size: 1.125rem;
color: var(--text-secondary);
max-width: 480px;
margin: 0 auto;
}
/* ─── DOMAIN FILTER ───────────────────────────────────────── */
.domain-filter {
display: flex;
gap: 8px;
justify-content: center;
margin-bottom: 32px;
flex-wrap: wrap;
}
.domain-filter button {
padding: 6px 16px;
border-radius: 20px;
border: 1px solid var(--border);
background: var(--bg-off);
color: var(--text-secondary);
font-size: .875rem;
font-weight: 500;
cursor: pointer;
transition: all .15s;
}
.domain-filter button:hover {
border-color: var(--blue);
color: var(--blue);
}
.domain-filter button.active {
background: var(--blue);
color: #fff;
border-color: var(--blue);
}
/* ─── LETTER SECTION ──────────────────────────────────────── */
.letter-section {
margin-bottom: 48px;
}
.letter-heading {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.letter-heading h2 {
font-size: 2rem;
font-weight: 800;
color: var(--text-secondary);
letter-spacing: -.04em;
line-height: 1;
width: 36px;
flex-shrink: 0;
}
.letter-count {
font-size: .8125rem;
color: var(--text-secondary);
}
/* ─── TERM CARD ───────────────────────────────────────────── */
.term-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px 32px;
margin-bottom: 16px;
transition: box-shadow .2s, transform .2s;
}
.term-card:hover {
box-shadow: var(--shadow-hover);
transform: translateY(-2px);
}
.term-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.term-name {
font-size: 1.3125rem;
font-weight: 700;
color: var(--text);
letter-spacing: -.02em;
line-height: 1.2;
}
.term-category {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: .75rem;
font-weight: 600;
letter-spacing: .02em;
text-transform: uppercase;
white-space: nowrap;
flex-shrink: 0;
margin-top: 2px;
}
.cat-platform { background: var(--blue-light); color: var(--blue); }
.cat-technical { background: var(--green-light); color: var(--green); }
.cat-payment { background: var(--purple-light); color: var(--purple); }
.cat-legal { background: var(--orange-light); color: var(--orange); }
.cat-core { background: var(--bg-off); color: var(--text-secondary); }
.cat-amos { background: var(--pink-light); color: var(--pink); }
.cat-landvex { background: var(--blue-light); color: var(--blue); }
.cat-quixzoom { background: var(--green-light); color: var(--green); }
.cat-product { background: var(--orange-light); color: var(--orange); }
.term-short {
font-size: 1rem;
font-weight: 500;
color: var(--text);
margin-bottom: 12px;
line-height: 1.5;
}
.term-long {
font-size: .9375rem;
color: var(--text-secondary);
line-height: 1.65;
margin-bottom: 16px;
}
.term-context {
background: var(--bg-off);
border-radius: var(--radius-sm);
padding: 14px 18px;
margin-bottom: 14px;
}
.term-context-label {
font-size: .75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--blue);
display: block;
margin-bottom: 6px;
}
.term-context p {
font-size: .875rem;
color: var(--text-secondary);
line-height: 1.6;
}
.term-related {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.term-related > span {
font-size: .8125rem;
color: var(--text-secondary);
}
.term-related a {
display: inline-block;
padding: 3px 10px;
border-radius: 20px;
font-size: .8125rem;
font-weight: 500;
color: var(--blue);
background: var(--blue-light);
text-decoration: none;
transition: background .12s, color .12s;
}
.term-related a:hover {
background: var(--blue);
color: #fff;
}
/* ─── SEARCH EMPTY ────────────────────────────────────────── */
.search-empty {
display: none;
text-align: center;
padding: 64px 24px;
color: var(--text-secondary);
font-size: 1rem;
}
.search-empty.visible { display: block; }
/* ─── LAST UPDATED ────────────────────────────────────────── */
.last-updated {
text-align: center;
font-size: .8125rem;
color: var(--text-secondary);
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
/* ─── RESPONSIVE ──────────────────────────────────────────── */
@media (max-width: 600px) {
.nav { gap: 10px; padding: 0 16px; }
.nav-az { display: none; }
.nav-search { max-width: none; flex: 1; }
.term-card { padding: 20px 18px; }
.page-header { padding: 40px 0 32px; }
.domain-filter { gap: 6px; }
.domain-filter button { padding: 5px 12px; font-size: .8125rem; }
}
@media (max-width: 900px) {
.nav-az { max-width: 200px; }
}
/* ─── HIGHLIGHT when anchor-targeted ─────────────────────── */
.term-card:target {
border-color: var(--blue);
box-shadow: 0 0 0 3px rgba(0,102,255,.15);
}
/* ─── HIDDEN during search/filter ─────────────────────────── */
.term-card.hidden { display: none; }
.letter-section.hidden { display: none; }
/* ─── LOADING STATE ───────────────────────────────────────── */
.loading {
text-align: center;
padding: 80px 24px;
color: var(--text-secondary);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border);
border-top-color: var(--blue);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<nav class="nav">
<a href="/" class="nav-logo">qui<span class="x">X</span>zoom</a>
<div class="nav-divider"></div>
<span class="nav-title">Glossary</span>
<div class="nav-search">
<svg class="nav-search-icon" width="14" height="14" viewBox="0 0 16 16" fill="none">
<circle cx="6.5" cy="6.5" r="5" stroke="currentColor" stroke-width="1.5"/>
<path d="M10 10l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<input type="search" id="glossary-search" placeholder="Search terms…" autocomplete="off" aria-label="Search glossary">
</div>
<nav class="nav-az" id="az-nav" aria-label="Jump to letter">
<!-- Populated by JS -->
</nav>
</nav>
<main>
<header class="page-header">
<h1>Every term.<br>Defined.</h1>
<p>The complete ecosystem reference — from field observations to decision intelligence. Covering quiXzoom, AMOS, and Landvex.</p>
</header>
<div class="domain-filter" id="domain-filter">
<button data-domain="all" class="active">All</button>
<button data-domain="quixzoom">quiXzoom</button>
<button data-domain="amos">AMOS</button>
<button data-domain="landvex">Landvex</button>
</div>
<div class="search-empty" id="search-empty">No terms match "<span id="search-query"></span>"</div>
<div id="glossary-content">
<div class="loading">
<div class="loading-spinner"></div>
<p>Loading glossary…</p>
</div>
</div>
<div class="last-updated" id="last-updated"></div>
</main>
<footer style="background:#0d0d10;border-top:1px solid rgba(255,255,255,.07);padding:32px 24px;margin-top:48px">
<div style="max-width:1080px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px">
<a href="/" style="font-size:1.1rem;font-weight:800;color:#fff;letter-spacing:-.03em;text-decoration:none">qui<span style="color:#3b82f6">X</span>zoom</a>
<nav style="display:flex;gap:20px;flex-wrap:wrap">
<a href="/" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">For Zoomers</a>
<a href="/for-organisations/" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">For Organisations</a>
<a href="/developer" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Developer</a>
<a href="/pricing" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Pricing</a>
<a href="/about" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">About</a>
<a href="/status" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Status</a>
<a href="/privacy" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Privacy</a>
<a href="/terms" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Terms</a>
<a href="/security" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Security</a>
<a href="/support" style="font-size:.8125rem;color:rgba(255,255,255,.75);transition:color .15s;text-decoration:none">Support</a>
</nav>
<p style="font-size:.8125rem;color:rgba(255,255,255,.75);margin:0">© 2026 Landvex Inc</p>
</div>
</footer>
<script>
// ─── GLOSSARY DATA ──────────────────────────────────────────────────
let glossaryData = null;
let currentDomain = 'all';
// ─── LOAD DATA ──────────────────────────────────────────────────────
async function loadGlossary() {
try {
const response = await fetch('glossary-data.json');
if (!response.ok) throw new Error('Failed to load glossary data');
glossaryData = await response.json();
renderGlossary();
updateLastUpdated();
} catch (error) {
document.getElementById('glossary-content').innerHTML = `
<div class="search-empty visible">
<p>Error loading glossary. Please refresh the page.</p>
<p style="font-size: .875rem; margin-top: 8px;">${error.message}</p>
</div>
`;
}
}
// ─── RENDER GLOSSARY ────────────────────────────────────────────────
function renderGlossary() {
if (!glossaryData) return;
const content = document.getElementById('glossary-content');
const terms = glossaryData.terms;
const categories = glossaryData.categories;
// Group terms by first letter
const grouped = {};
terms.forEach(term => {
const letter = term.name.charAt(0).toUpperCase();
if (!grouped[letter]) grouped[letter] = [];
grouped[letter].push(term);
});
// Sort letters
const letters = Object.keys(grouped).sort();
// Build A-Z nav
const azNav = document.getElementById('az-nav');
azNav.innerHTML = letters.map(letter =>
`<a href="#${letter.toLowerCase()}">${letter}</a>`
).join('');
// Build content
let html = '';
letters.forEach(letter => {
const letterTerms = grouped[letter].sort((a, b) => a.name.localeCompare(b.name));
html += `
<section class="letter-section" id="${letter.toLowerCase()}">
<div class="letter-heading">
<h2>${letter}</h2>
<span class="letter-count">${letterTerms.length} term${letterTerms.length !== 1 ? 's' : ''}</span>
</div>
${letterTerms.map(term => renderTermCard(term, categories)).join('')}
</section>
`;
});
content.innerHTML = html;
// Setup intersection observer for A-Z active state
setupAZObserver();
}
// ─── RENDER TERM CARD ───────────────────────────────────────────────
function renderTermCard(term, categories) {
const cat = categories[term.category] || categories.platform;
const relatedLinks = term.related.map(id => {
const relatedTerm = glossaryData.terms.find(t => t.id === id);
return relatedTerm ? `<a href="#${id}">${relatedTerm.name}</a>` : '';
}).join('');
return `
<article id="${term.id}" class="term-card" data-domains="${term.domains.join(' ')}">
<div class="term-header">
<h3 class="term-name">${term.name}</h3>
<span class="term-category cat-${term.category}">${cat.label}</span>
</div>
<p class="term-short">${term.short}</p>
<p class="term-long">${term.long}</p>
<div class="term-context">
<span class="term-context-label">${term.contextLabel}</span>
<p>${term.context}</p>
</div>
<div class="term-related">
<span>Related:</span>
${relatedLinks}
</div>
</article>
`;
}
// ─── UPDATE LAST UPDATED ────────────────────────────────────────────
function updateLastUpdated() {
if (!glossaryData?.lastUpdated) return;
const date = new Date(glossaryData.lastUpdated);
document.getElementById('last-updated').textContent =
`Last updated: ${date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`;
}
// ─── SEARCH ─────────────────────────────────────────────────────────
const searchInput = document.getElementById('glossary-search');
const emptyMsg = document.getElementById('search-empty');
const querySpan = document.getElementById('search-query');
searchInput.addEventListener('input', () => {
const q = searchInput.value.trim().toLowerCase();
if (!q) {
document.querySelectorAll('.term-card, .letter-section').forEach(el => el.classList.remove('hidden'));
emptyMsg.classList.remove('visible');
return;
}
let anyVisible = false;
document.querySelectorAll('.letter-section').forEach(section => {
let sectionHasVisible = false;
section.querySelectorAll('.term-card').forEach(card => {
const text = card.textContent.toLowerCase();
const id = card.id.toLowerCase();
const match = text.includes(q) || id.includes(q);
card.classList.toggle('hidden', !match);
if (match) sectionHasVisible = true;
});
section.classList.toggle('hidden', !sectionHasVisible);
if (sectionHasVisible) anyVisible = true;
});
querySpan.textContent = searchInput.value.trim();
emptyMsg.classList.toggle('visible', !anyVisible);
});
// ─── DOMAIN FILTER ──────────────────────────────────────────────────
document.getElementById('domain-filter').addEventListener('click', (e) => {
if (!e.target.matches('button')) return;
document.querySelectorAll('.domain-filter button').forEach(btn => btn.classList.remove('active'));
e.target.classList.add('active');
currentDomain = e.target.dataset.domain;
filterByDomain();
});
function filterByDomain() {
if (!glossaryData) return;
let anyVisible = false;
document.querySelectorAll('.letter-section').forEach(section => {
let sectionHasVisible = false;
section.querySelectorAll('.term-card').forEach(card => {
const domains = card.dataset.domains?.split(' ') || [];
const match = currentDomain === 'all' || domains.includes(currentDomain);
card.classList.toggle('hidden', !match);
if (match) sectionHasVisible = true;
});
section.classList.toggle('hidden', !sectionHasVisible);
if (sectionHasVisible) anyVisible = true;
});
emptyMsg.classList.toggle('visible', !anyVisible);
if (!anyVisible) {
querySpan.textContent = currentDomain === 'all' ? '' : `domain: ${currentDomain}`;
}
}
// ─── A-Z ACTIVE LETTER TRACKING ─────────────────────────────────────
function setupAZObserver() {
const azLinks = document.querySelectorAll('.nav-az a');
const sections = Array.from(document.querySelectorAll('.letter-section[id]'));
if (sections.length === 0) return;
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.id;
azLinks.forEach(a => {
a.classList.toggle('active', a.getAttribute('href') === '#' + id);
});
}
});
}, { rootMargin: '-20% 0px -70% 0px' });
sections.forEach(s => observer.observe(s));
}
// ─── INITIALIZE ─────────────────────────────────────────────────────
loadGlossary();
</script>
</body>
</html>