- slide 02: four text-only 'missing' ghosts replaced by a large 'Missing Pieces' title over a narrower center column of four dashed rects, each opening with a bordeaux question mark - title now reads 'four missing pieces' (matches the center stack) - kicker 'Twenty years ago' removed - deck.css: flex fix for Reveal inline display, snapshot/chart placeholder grids, vision-slide rhythm
351 lines
21 KiB
HTML
351 lines
21 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>PROTOTYPE · Scatter layouts — emblem icons (slides 02 & 03)</title>
|
||
<link rel="stylesheet" href="../deck/fonts.css">
|
||
<style>
|
||
:root {
|
||
--bordeaux: #cb333b; --bordeaux-deep: #8f1204; --bordeaux-tint: #f4dfe1;
|
||
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
|
||
--ink: #2a2426; --ink-muted: #6b6568;
|
||
--line: #e3e0e1; --line-strong: #c9c5c6;
|
||
--sans: 'Source Sans 3', system-ui, sans-serif;
|
||
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { background: var(--surface-sunken); font-family: var(--sans); padding: 36px 0 90px; color: var(--ink); }
|
||
|
||
.note { width: 1280px; margin: 0 auto 18px; padding: 12px 18px; background: #fff8e6; border: 1px solid #e8d9a8;
|
||
border-radius: 8px; font: 400 13px/1.5 var(--sans); color: #6b5d2a; }
|
||
.note b { font-weight: 700; }
|
||
|
||
.switcher { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
|
||
display: flex; align-items: center; gap: 6px; padding: 8px 10px; background: #2a2426;
|
||
border-radius: 9999px; box-shadow: 0 6px 24px rgba(0,0,0,.25); z-index: 10; white-space: nowrap; }
|
||
.switcher .tag { font: 700 10px/1 var(--mono); letter-spacing: .1em; color: #9c9598; padding: 0 10px; }
|
||
.switcher a { display: inline-flex; height: 30px; align-items: center; padding: 0 14px; border-radius: 9999px;
|
||
font: 700 12px/1 var(--sans); color: #fdfcfc; text-decoration: none; opacity: .75; }
|
||
.switcher a.active { background: var(--bordeaux); opacity: 1; }
|
||
.switcher .who { font: 400 11px/1 var(--sans); color: #9c9598; padding: 0 12px 0 8px; max-width: 430px; white-space: normal; }
|
||
|
||
.slide { position: relative; width: 1280px; height: 720px; margin: 0 auto 34px;
|
||
overflow: hidden; background: var(--surface); color: var(--ink); border: 1px solid var(--line);
|
||
border-radius: 8px; display: flex; flex-direction: column; }
|
||
.head { flex: 0 0 104px; display: flex; align-items: center; justify-content: space-between;
|
||
padding: 0 40px; background: var(--surface); border-bottom: 1px solid var(--line); }
|
||
.head img { height: 80px; display: block; }
|
||
.head-org { font: 700 11.5px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted); }
|
||
.band { flex: 0 0 46px; display: flex; align-items: center; justify-content: space-between;
|
||
padding: 0 40px; background: var(--bordeaux); color: var(--surface); }
|
||
.band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
|
||
.foot { flex: 0 0 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
|
||
padding: 0 40px; border-top: 1px solid var(--line); font: 600 10px/1.4 var(--sans);
|
||
letter-spacing: .03em; color: var(--ink-muted); }
|
||
.foot span { white-space: nowrap; }
|
||
.foot span:first-child { overflow: hidden; text-overflow: ellipsis; }
|
||
.sbody { flex: 1; display: flex; flex-direction: column; padding: 26px 48px 16px; }
|
||
.kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
|
||
color: var(--bordeaux); margin-bottom: 12px; }
|
||
h2 { font: 700 30px/1.15 var(--sans); color: var(--ink); margin: 0 0 6px; }
|
||
.lead { font: 400 15px/1.5 var(--sans); color: var(--ink-muted); margin: 4px 0 0; }
|
||
|
||
/* ===== scattered islands canvas ===== */
|
||
.scatter { position: relative; flex: 1; min-height: 0; }
|
||
.isle { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
||
cursor: pointer; transform: rotate(var(--rot, 0deg)); transition: transform .18s ease; border: none; background: none;
|
||
font-family: var(--sans); width: var(--w, 240px); }
|
||
.isle:hover { transform: rotate(var(--rot, 0deg)) scale(1.04); }
|
||
.isle:focus-visible { outline: none; transform: rotate(var(--rot, 0deg)) scale(1.04); }
|
||
.isle .ico { width: var(--size, 140px); height: var(--size, 140px); color: var(--bordeaux); display: block; }
|
||
.isle .nm { font: 700 16px/1.3 var(--sans); color: var(--ink); margin-top: 4px; }
|
||
.isle .sc { font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); max-width: 240px; }
|
||
.isle.ghost .ico { color: var(--ink-muted); opacity: .5; }
|
||
.isle.ghost .nm { color: var(--bordeaux-deep); font-size: 14px; }
|
||
.isle.ghost .sc { font-style: italic; }
|
||
|
||
/* slide 03 existed zone */
|
||
.flow { display: flex; align-items: stretch; gap: 14px; flex: 1; min-height: 0; margin-top: 6px; }
|
||
.zone { border-radius: 8px; position: relative; }
|
||
.zone > .zlabel { position: absolute; top: 8px; left: 14px; font: 700 10px/1 var(--mono);
|
||
letter-spacing: .14em; text-transform: uppercase; }
|
||
.zone.existed { flex: 0 0 250px; background: var(--surface-alt); border: 1px dashed var(--line-strong); position: relative; }
|
||
.zone.existed > .zlabel { color: var(--ink-muted); }
|
||
.zone.built { flex: 1; background: #faf4f5; border: 1px solid #e7c3c6; }
|
||
.zone.built > .zlabel { color: var(--bordeaux); }
|
||
.zone.built .row { display: flex; align-items: center; gap: 8px; height: 100%; padding: 0 10px; }
|
||
.fcol { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
||
.fbox { background: #fff; border: 1px solid var(--line-strong); border-radius: 6px; padding: 9px 8px; text-align: center; }
|
||
.fbox b { display: block; font: 700 12px/1.25 var(--sans); }
|
||
.fbox span { display: block; margin-top: 2px; font: 400 10px/1.3 var(--sans); color: var(--ink-muted); }
|
||
.fbox.end { background: var(--bordeaux); border-color: var(--bordeaux); color: #fff; }
|
||
.fbox.end span { color: rgba(255,255,255,.85); }
|
||
.farrow { align-self: center; color: var(--bordeaux); font: 700 14px/1 var(--sans); }
|
||
.src-scatter { position: relative; flex: 1; min-height: 0; }
|
||
.src-scatter .isle { --w: 120px; position: absolute; }
|
||
.src-scatter .isle .ico { width: var(--size, 64px); height: var(--size, 64px); }
|
||
.src-scatter .isle .nm { font-size: 11.5px; }
|
||
|
||
|
||
.todo { display: flex; align-items: center; gap: 12px; border: 2px dashed var(--bordeaux); border-radius: 8px; background: #fff; padding: 10px 14px;
|
||
margin-bottom: 10px; box-shadow: 0 2px 8px rgba(42,36,38,.06); }
|
||
.todo .q { font: 800 26px/1 var(--sans); color: var(--bordeaux-deep); }
|
||
.todo .tx { display: flex; flex-direction: column; }
|
||
.todo b { font: 700 15px/1.3 var(--sans); color: var(--bordeaux-deep); }
|
||
.todo .tx span { font: 400 12px/1.4 var(--sans); color: var(--ink-muted); margin-top: 2px; }
|
||
.missing-title { position: absolute; font: 700 30px/1.2 var(--sans); color: var(--bordeaux-deep); }
|
||
|
||
.todo-stack { position: absolute; z-index: 2; }
|
||
.isle .ico { color: var(--bordeaux); }
|
||
.isle.ghost .ico { color: #9a938f; }
|
||
.isle .nm { font-size: 15px; }
|
||
.isle .sc { font-size: 13.5px; max-width: 240px; }
|
||
|
||
/* popup */
|
||
.ai-pop { position: fixed; inset: 0; z-index: 100; background: rgba(42,36,38,.45);
|
||
display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity .2s; }
|
||
.ai-pop.open { opacity: 1; pointer-events: auto; }
|
||
.ai-pop-card { position: relative; width: min(560px, 92%); background: #fff; border: 1px solid var(--line);
|
||
border-radius: 12px; box-shadow: 0 18px 50px rgba(42,36,38,.28); padding: 24px 28px 22px;
|
||
transform: translateY(14px) scale(.95); transition: transform .24s cubic-bezier(.2,.9,.3,1.15); }
|
||
.ai-pop.open .ai-pop-card { transform: none; }
|
||
.ai-pop-kicker { font: 700 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 10px; }
|
||
.ai-pop-title { font: 700 18px/1.3 var(--sans); color: var(--ink); margin-bottom: 12px; }
|
||
.ai-pop-card p { font: 400 14px/1.65 var(--sans); color: var(--ink); margin: 0 0 12px; }
|
||
.pop-list { list-style: none; display: flex; flex-direction: column; gap: 9px; }
|
||
.pop-list li { position: relative; padding-left: 20px; font: 400 13.5px/1.5 var(--sans); }
|
||
.pop-list li::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px; border-radius: 9999px; background: var(--bordeaux); }
|
||
.pop-list b { font-weight: 700; }
|
||
.roi { margin-top: 14px; border: 1.5px solid var(--bordeaux); background: #faf4f5; border-radius: 8px; padding: 10px 14px; font: 400 13.5px/1.55 var(--sans); }
|
||
.roi .k { display: block; margin-bottom: 4px; font: 800 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux-deep); }
|
||
.ai-pop-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; border: none; border-radius: 9999px;
|
||
background: var(--surface-alt); font: 700 15px/1 var(--sans); color: var(--ink-muted); cursor: pointer; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="note"><b>EVALUATION</b> — icone Emblems (le tue preferite), <b>più grandi, senza bordi/card</b>, disposte in modo
|
||
<b>sparsamente organico</b> invece che a griglia. Tre composizioni per slide 02 e adattamento alla 03.</div>
|
||
|
||
<main id="deck"></main>
|
||
|
||
<nav class="switcher">
|
||
<span class="tag">PROTOTYPE</span>
|
||
<a href="?s=1" data-v="1">A · Constellation</a>
|
||
<a href="?s=2" data-v="2">B · Cascade</a>
|
||
<a href="?s=3" data-v="3">C · Orbit</a>
|
||
<span class="who" id="who"></span>
|
||
</nav>
|
||
|
||
<script>
|
||
/* ---- emblem icons (style 3, winner) ---- */
|
||
const EMB = {
|
||
cardioref: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
|
||
<path d="M24 33 c-6-3.5-9-7-9-10.5 0-3 4-5.5 7-3 1 .8 1.6 1.8 2 3 .4-1.2 1-2.2 2-3 3-2.5 7 0 7 3 0 3.5-3 7-9 10.5z" fill="currentColor" stroke="none"/>
|
||
<path d="M22.5 21 h3 v3 h3 v3 h-3 v3 h-3 v-3 h-3 v-3 h3 z" fill="#fff" stroke="none"/>`,
|
||
genetic: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
|
||
<path d="M18 8 C28 14 28 20 18 26 C12 30 12 35 18 40"/>
|
||
<path d="M30 8 C20 14 20 20 30 26 C36 30 36 35 30 40"/>
|
||
<path d="M17 12 H31"/><path d="M15.5 18 H32.5"/><path d="M17 24 H31"/><path d="M14.5 31 H22"/>
|
||
<rect x="25" y="29" width="12" height="12" rx="1.5" stroke-width="2"/>
|
||
<path d="M25 34 H37"/><path d="M30.5 29 V41"/>`,
|
||
omics: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
|
||
<circle cx="24" cy="24" r="4" fill="currentColor" stroke="none"/>
|
||
<circle cx="24" cy="10" r="3.4"/><circle cx="11.5" cy="31" r="3.4"/><circle cx="36.5" cy="31" r="3.4"/>
|
||
<path d="M24 20.5 V13.5"/><path d="M21 26.5 L14 29.5"/><path d="M27 26.5 L34 29.5"/>`,
|
||
ecg: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
|
||
<path d="M9 24 h6 l3-9 5 16 3-7 h14" stroke-width="2.4"/>
|
||
<circle cx="24" cy="24" r="1.8" fill="currentColor" stroke="none"/>`,
|
||
};
|
||
const GHOST = `<circle cx="24" cy="24" r="20" stroke-dasharray="5 4" stroke-width="2.4"/>
|
||
<path d="M15 33 L33 15" stroke-width="2.4"/><circle cx="17" cy="17" r="2.4" fill="currentColor" stroke="none"/>`;
|
||
|
||
const ico = (key, size) =>
|
||
`<svg class="ico" viewBox="0 0 48 48" style="width:${size}px;height:${size}px" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">${key === 'ghost' ? GHOST : EMB[key]}</svg>`;
|
||
|
||
const SYS = [
|
||
{ key: 'cardioref', name: 'Cardioref', scope: 'visits · operations · reports — twenty years of management on structured forms' },
|
||
{ key: 'genetic', name: 'Genetic data', scope: 'DNA instruments — results typed into Excel by hand' },
|
||
{ key: 'omics', name: 'Omics Portal', scope: 'meant to host Cardioref + genetics — still immature' },
|
||
{ key: 'ecg', name: 'ECG', scope: 'paper strip + a CSV on request' },
|
||
];
|
||
const GHOSTS = [
|
||
{ name: 'No Health Intelligence', scope: 'nothing for historical data' },
|
||
{ name: 'No ML-ready datamarts', scope: 'nothing for predictive statistics' },
|
||
];
|
||
|
||
const CONTRIBS = {
|
||
cardioref: { kicker: 'The starting point · 20 years ago', title: 'Cardioref — the daily workhorse',
|
||
body: `<p class="lead">A traditional relational application: classic forms for the typical hospital procedures.</p>
|
||
<ul class="pop-list"><li><b>Used for</b> — visits, operations, reports: the daily management of the Unit</li>
|
||
<li><b>Good at it</b> — twenty years of patient management on structured records</li>
|
||
<li><b>The catch</b> — the clinical story lives in free-text fields, not in the boxes designed for it</li></ul>
|
||
<div class="roi"><b class="k">Problem</b>Almost unusable for research: the knowledge is there — locked in prose.</div>` },
|
||
genetic: { kicker: 'The starting point · 20 years ago', title: 'Genetic data — sophisticated in, Excel out',
|
||
body: `<p class="lead">The DNA instruments are state-of-the-art. What happens after them is not.</p>
|
||
<ul class="pop-list"><li><b>Used for</b> — running the genetic analyses the Unit prescribes</li>
|
||
<li><b>The catch</b> — results are transcribed by hand into Excel files</li>
|
||
<li><b>Never connected</b> — Cardioref has no idea genetics exists</li></ul>
|
||
<div class="roi"><b class="k">Problem</b>Two parallel worlds: genotypes on one side, clinical records on the other.</div>` },
|
||
omics: { kicker: 'The starting point · 20 years ago', title: 'Omics Portal — the right idea, too soon',
|
||
body: `<p class="lead">A portal meant to host both Cardioref and the genetic data — the seed of AritmoLab.</p>
|
||
<ul class="pop-list"><li><b>Used for</b> — the first 360° patient-management features</li>
|
||
<li><b>The catch</b> — functionality still immature</li>
|
||
<li><b>Never connected</b> — it could not see the data it was meant to host</li></ul>
|
||
<div class="roi"><b class="k">Problem</b>A promising idea without a bridge: too young to carry the data.</div>` },
|
||
ecg: { kicker: 'The starting point · 20 years ago', title: 'ECG — paper, and a CSV if you ask',
|
||
body: `<p class="lead">Classic ECG devices: they print the strip, and they can export a file.</p>
|
||
<ul class="pop-list"><li><b>Used for</b> — acquiring and viewing ECG traces</li>
|
||
<li><b>The catch</b> — no machine-to-machine communication</li>
|
||
<li><b>The only door out</b> — a CSV file, produced on request</li></ul>
|
||
<div class="roi"><b class="k">Problem</b>Every ECG meant a manual download — automation was impossible by design.</div>` },
|
||
};
|
||
|
||
const chrome = (band) => `
|
||
<header class="head"><img src="logo.png" alt=""><span class="head-org">AritmoLab · Policlinico San Donato</span></header>
|
||
<nav class="band"><span class="band-section">${band}</span><span class="jump">0X · …</span></nav>`;
|
||
const foot = `<footer class="foot"><span>Role of AI in the Analysis of Unstructured Clinical Databases · Dr. Marco Pancotti - MultiPhysixLab · Dr. Sara Paratico - Gruppo San Donato · San Donato Milanese, Milan, Italy · 2–3 October 2026</span><span>0X / 15</span></footer>`;
|
||
|
||
function isle(s, size, rot, x, y) {
|
||
const pos = x ? `left:${x}; top:${y};` : '';
|
||
return `<button class="isle" data-start="${s.key}" style="--rot:${rot}deg; --size:${size}px; ${pos}">
|
||
${ico(s.key, size)}<span class="nm">${s.name}</span><span class="sc">${s.scope}</span></button>`;
|
||
}
|
||
function ghostIsle(g, size, rot, x, y) {
|
||
const pos = x ? `left:${x}; top:${y};` : '';
|
||
return `<div class="isle ghost" style="--rot:${rot}deg; --size:${size}px; ${pos}">
|
||
${ico('ghost', size)}<span class="nm">${g.name}</span><span class="sc">${g.scope}</span></div>`;
|
||
}
|
||
|
||
/* ---- A · Constellation: scattered islands (hand-placed, organic jitter) ---- */
|
||
const TODO_STACK = [
|
||
['Clinical Intelligence', 'dashboards on the clinical history of the Unit and its patients'],
|
||
['Datawarehouse', 'the single source of truth'],
|
||
['ML-ready data', 'cohort tables, ready for model training'],
|
||
['Professional Portal', 'AritmoLab — the grown-up Omics Portal'],
|
||
];
|
||
|
||
/* three placements of the central "to build" rectangles */
|
||
const ORBIT_CFG = {
|
||
1: { isles: [
|
||
{ key: 'cardioref', x: '0%', y: '2%', rot: -3, size: 124 },
|
||
{ key: 'genetic', x: '72%', y: '2%', rot: 3, size: 118 },
|
||
{ key: 'omics', x: '0%', y: '56%', rot: -2, size: 120 },
|
||
{ key: 'ecg', x: '72%', y: '56%', rot: 2, size: 118 } ],
|
||
stack: { left: '27%', top: '20%', width: '46%' }, offsets: ['0%', '8%', '4%', '10%'], icon: 116 },
|
||
2: { isles: [
|
||
{ key: 'cardioref', x: '36%', y: '4%', rot: -3, size: 118 },
|
||
{ key: 'genetic', x: '64%', y: '18%', rot: 3, size: 112 },
|
||
{ key: 'omics', x: '36%', y: '48%', rot: -2, size: 114 },
|
||
{ key: 'ecg', x: '63%', y: '60%', rot: 2, size: 112 } ],
|
||
stack: { left: '3%', top: '16%', width: '25%' }, offsets: ['0%', '10%', '4%', '12%'], icon: 106 },
|
||
3: { isles: [
|
||
{ key: 'cardioref', x: '1%', y: '6%', rot: -3, size: 120 },
|
||
{ key: 'genetic', x: '75%', y: '6%', rot: 3, size: 114 },
|
||
{ key: 'omics', x: '1%', y: '56%', rot: -2, size: 118 },
|
||
{ key: 'ecg', x: '75%', y: '56%', rot: 2, size: 114 } ],
|
||
stack: { left: '32%', top: '22%', width: '35%' }, offsets: ['0%', '7%', '4%', '9%'], icon: 114, grid: true },
|
||
};
|
||
|
||
function orbitSlide02(v) {
|
||
const cfg = ORBIT_CFG[v];
|
||
let h = `<div class="todo-stack" style="left:${cfg.stack.left}; top:${cfg.stack.top}; width:${cfg.stack.width}">` +
|
||
TODO_STACK.map(([n, c], i) => `<div class="todo" style="margin-left:${cfg.offsets[i]}"><span class="q">?</span><span class="tx"><b>${n}</b><span>${c}</span></span></div>`).join('') + `</div>`;
|
||
for (const s of cfg.isles) {
|
||
const sys = SYS.find(vv => vv.key === s.key);
|
||
h += `<button class="isle" data-start="${s.key}" style="--rot:${s.rot}deg; --size:${s.size}px; left:${s.x}; top:${s.y}; width:230px">
|
||
${ico(s.key, s.size)}<span class="nm">${sys.name}</span><span class="sc">${sys.scope}</span></button>`;
|
||
}
|
||
h += `<div class="missing-title" style="left:calc(${cfg.stack.left} + 14px); top:calc(${cfg.stack.top} - 46px); width:${cfg.stack.width}">Missing Pieces</div>`;
|
||
return h;
|
||
}
|
||
|
||
function layoutZone(v) {
|
||
const items = [
|
||
['cardioref', 62, -3, '2%', '0%'],
|
||
['genetic', 62, 3, '52%', '16%'],
|
||
['omics', 62, -2, '4%', '38%'],
|
||
['ecg', 62, 3, '54%', '60%'],
|
||
];
|
||
let h = items.map(([k, size, rot, x, y]) => {
|
||
const s = SYS.find(vv => vv.key === k);
|
||
return `<div class="isle" style="--rot:${rot}deg; --size:${size}px; left:${x}; top:${y}; width:170px">
|
||
${ico(k, size)}<span class="nm">${s.name}</span></div>`;
|
||
}).join('');
|
||
h += `<div class="isle ghost" style="--rot:-2deg; --size:56px; left:32%; top:82%; width:170px">
|
||
${ico('ghost', 56)}<span class="nm" style="color:var(--bordeaux-deep)">missing</span></div>`;
|
||
return h;
|
||
}
|
||
|
||
const LAYOUTS = {
|
||
1: { desc: 'A · Center stack: i quattro obiettivi impilati al centro, i sistemi in orbita attorno.' },
|
||
2: { desc: 'B · Side column: la pila degli obiettivi a sinistra, i sistemi orbitano più larghi a destra.' },
|
||
3: { desc: 'C · Compact 2×2: griglia compatta e sfalsata al centro, orbita larga.' },
|
||
};
|
||
const q = new URLSearchParams(location.search);
|
||
const v = ['1','2','3'].includes(q.get('s')) ? q.get('s') : '1';
|
||
document.body.className = 's' + v;
|
||
document.querySelectorAll('.switcher a').forEach(a => a.classList.toggle('active', a.dataset.v === v));
|
||
document.getElementById('who').textContent = LAYOUTS[v].desc;
|
||
document.getElementById('deck').innerHTML = `
|
||
<section class="slide">
|
||
${chrome('Where we started')}
|
||
<div class="sbody">
|
||
<h2>Four islands and four missing pieces</h2>
|
||
<div class="scatter">${orbitSlide02(v)}</div>
|
||
</div>
|
||
${foot}
|
||
</section>
|
||
<section class="slide">
|
||
${chrome('The plan')}
|
||
<div class="sbody">
|
||
<div class="kicker">The plan · what we wanted to build</div>
|
||
<h2>One platform, two destinations</h2>
|
||
<div class="flow">
|
||
<div class="zone existed">
|
||
<span class="zlabel">What existed — to connect</span>
|
||
<div class="src-scatter">${layoutZone(v)}</div>
|
||
</div>
|
||
<div class="farrow">→</div>
|
||
<div class="zone built">
|
||
<span class="zlabel">What we wanted to build</span>
|
||
<div class="row">
|
||
<div class="fcol"><div class="fbox"><b>Staging</b><span>raw replica</span></div></div>
|
||
<div class="farrow">→</div>
|
||
<div class="fcol"><div class="fbox"><b>Integration</b><span>clean · text mining</span></div></div>
|
||
<div class="farrow">→</div>
|
||
<div class="fcol"><div class="fbox"><b>Star schema</b><span>warehouse</span></div></div>
|
||
<div class="farrow">→</div>
|
||
<div class="fcol"><div class="fbox"><b>Datamarts</b><span>for research</span></div></div>
|
||
<div class="farrow">→</div>
|
||
<div class="fcol"><div class="fbox end"><b>AritmoLab</b><span>portal + warehouse</span></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
${foot}
|
||
</section>`;
|
||
|
||
const pop = document.createElement('div');
|
||
pop.className = 'ai-pop';
|
||
pop.innerHTML = `<div class="ai-pop-card" role="dialog"><button class="ai-pop-x">✕</button>
|
||
<div class="ai-pop-kicker" id="kick"></div><div class="ai-pop-title" id="ttl"></div><div id="bdy"></div></div>`;
|
||
document.body.appendChild(pop);
|
||
document.addEventListener('click', (e) => {
|
||
const s = e.target.closest('[data-start]');
|
||
if (s) {
|
||
const c = CONTRIBS[s.dataset.start];
|
||
document.getElementById('kick').textContent = c.kicker;
|
||
document.getElementById('ttl').textContent = c.title;
|
||
document.getElementById('bdy').innerHTML = c.body;
|
||
pop.classList.add('open');
|
||
return;
|
||
}
|
||
if (e.target.closest('.ai-pop-x') || e.target === pop) pop.classList.remove('open');
|
||
});
|
||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') pop.classList.remove('open'); });
|
||
</script>
|
||
</body>
|
||
</html>
|