Deploy MEIDNet Studio
Browse files
meidnet/studio/landing.html
CHANGED
|
@@ -20,12 +20,13 @@
|
|
| 20 |
@font-face{font-family:'STIX Two Text';font-style:normal;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-normal-greek.woff2) format('woff2');unicode-range:U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF}
|
| 21 |
@font-face{font-family:'STIX Two Text';font-style:italic;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-italic-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
| 22 |
@font-face{font-family:'STIX Two Text';font-style:italic;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-italic-greek.woff2) format('woff2');unicode-range:U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF}
|
|
|
|
| 23 |
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/docs/assets/fonts/ibmplexmono-normal-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
| 24 |
:root{--c-data:#2a78d6;--c-model:#eb6834;--c-family:#1baf7a;--c-rules:#eda100;--c-targets:#e87ba4;--c-search:#008300;--c-candidates:#4a3aa7;
|
| 25 |
--p1:#4f46e5;--p2:#9333ea;--bg:#f6f7f5;--card:#fff;--ink:#0b0b0b;--muted:#52514e;--faint:#898781;--line:#e1e0d9;
|
| 26 |
--link:#4f46e5;--eyebrow-mix:#000;
|
| 27 |
--serif:'STIX Two Text','Cambria','Sitka Text',Georgia,serif;--math:'STIX Two Text','Cambria Math','STIX Two Math','Latin Modern Math',serif;
|
| 28 |
-
--mono:'IBM Plex Mono',ui-monospace,'Cascadia Mono',Consolas,monospace}
|
| 29 |
:root[data-theme="dark"]{--bg:#111417;--card:#1b1f24;--ink:#fff;--muted:#c3c2b7;--faint:#898781;--line:#2c2c2a;--link:#a5b4fc;--eyebrow-mix:#fff}
|
| 30 |
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--bg:#111417;--card:#1b1f24;--ink:#fff;--muted:#c3c2b7;--faint:#898781;--line:#2c2c2a;--link:#a5b4fc;--eyebrow-mix:#fff}}
|
| 31 |
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
@@ -37,28 +38,39 @@ header .wrap{display:flex;align-items:center;gap:10px;min-height:56px;flex-wrap:
|
|
| 37 |
nav{margin-left:auto;display:flex;gap:2px;flex-wrap:wrap}nav a{text-decoration:none;color:var(--muted);padding:6px 9px;border-radius:8px;font-size:14px;font-weight:600;white-space:nowrap}
|
| 38 |
nav a:hover,nav a[aria-current]{background:var(--bg);color:var(--ink)}nav a.cta{background:var(--p1);color:#fff}nav a.cta:hover{background:var(--p2);color:#fff}
|
| 39 |
@media(max-width:1080px){nav{order:3;width:100%;margin-left:0;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding:0 0 8px}nav::-webkit-scrollbar{display:none}header .theme{margin-left:auto}}
|
| 40 |
-
.hero{
|
| 41 |
-
|
|
|
|
|
|
|
| 42 |
.hero>*{min-width:0}
|
| 43 |
-
h1{font:
|
| 44 |
-
h1 .
|
| 45 |
-
|
| 46 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
.btns{display:flex;gap:10px;flex-wrap:wrap;margin:0 0 16px}
|
| 48 |
.btn{display:inline-flex;align-items:center;gap:8px;border-radius:12px;padding:12px 18px;font-weight:700;text-decoration:none;border:1px solid var(--line);background:var(--card);color:var(--ink);font-size:16px}
|
| 49 |
.btn.primary{background:var(--p1);border-color:var(--p1);color:#fff}.btn.primary:hover{background:var(--p2);border-color:var(--p2)}.btn:hover{box-shadow:0 4px 14px -8px rgba(0,0,0,.5)}
|
| 50 |
.pub{font:15.5px/1.5 var(--serif);color:var(--muted);margin:0}.pub b{color:var(--ink)}
|
| 51 |
-
.
|
| 52 |
-
.
|
| 53 |
-
.
|
| 54 |
-
.
|
| 55 |
-
.
|
| 56 |
-
.
|
| 57 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
.frac{display:inline-flex;flex-direction:column;align-items:center;vertical-align:middle;margin:0 .15em;line-height:1.15}
|
| 59 |
.frac>span:first-child{border-bottom:1px solid currentColor;padding:0 .2em .05em}.frac>span:last-child{padding:.08em .2em 0}
|
| 60 |
-
.eqs .note{font:400 12.5px/1.4 var(--serif);font-style:normal;color:var(--faint);white-space:normal}
|
| 61 |
-
@media(max-width:480px){.eqs{gap:8px 10px;grid-template-columns:4.4rem minmax(0,1fr)}.eqs dd{font-size:14.5px}}
|
| 62 |
.video{margin:12px 0 28px}.video video{width:100%;height:auto;border-radius:14px;border:1px solid var(--line);background:#000;display:block}
|
| 63 |
.video figcaption{font-size:14px;color:var(--muted);margin-top:8px}
|
| 64 |
.video video[hidden]{display:none}
|
|
@@ -109,16 +121,26 @@ footer{color:var(--faint);font-size:13.5px;padding:10px 0 40px;display:flex;gap:
|
|
| 109 |
<main class="wrap">
|
| 110 |
<section class="hero">
|
| 111 |
<div>
|
| 112 |
-
|
| 113 |
-
<
|
|
|
|
| 114 |
<div class="btns"><a class="btn primary" href="/studio/">▶ Try MEIDNet</a><a class="btn" href="/studio/?panel=data">Use your dataset</a><a class="btn" href="/docs/learn/index.html">Learn multimodality</a></div>
|
| 115 |
<p class="pub">A. Babu <i>et al.</i>, <i>npj Comput. Mater.</i> <b>12</b>, 287 (2026) · <a href="https://doi.org/10.1038/s41524-026-02153-3">Article</a> · <a href="https://github.com/ABnano/MEIDNet">Code</a></p>
|
| 116 |
</div>
|
| 117 |
-
<div class="heroart">
|
| 118 |
-
<dl class="eqs" aria-label="Representation learning" style="--k:var(--c-model)"><dt style="--k:var(--c-model)">Encode</dt><dd>m<sub>ij</sub> = φ<sub>e</sub>(h<sub>i</sub>, h<sub>j</sub>, ‖<b>x</b><sub>i</sub> − <b>x</b><sub>j</sub>‖²)</dd><dt></dt><dd>h<sub>i</sub> ← φ<sub>h</sub>(h<sub>i</sub>, Σ<sub>j</sub> m<sub>ij</sub>)</dd><dt style="--k:var(--c-family)">Align</dt><dd>s<sub>ij</sub> = cos(z<sub>s,i</sub>, z<sub>p,j</sub>)</dd><dt></dt><dd>ℒ = −log <span class="frac"><span>exp(s<sub>ii</sub>/τ)</span><span>Σ<sub>j</sub> exp(s<sub>ij</sub>/τ)</span></span></dd><dt style="--k:var(--p2)">Fuse</dt><dd>z = ½ (z<sub>s</sub> + z<sub>p</sub>) <span class="up">∈ ℝ<sup>128</sup></span></dd></dl>
|
| 119 |
-
<img class="prismfig" src="/docs/assets/meidnet_prism_logo.png" alt="MEIDNet Prism: several modalities enter a prism and leave it as one connected representation of materials.">
|
| 120 |
-
<dl class="eqs" aria-label="Inverse design"><dt style="--k:var(--c-targets)">Design</dt><dd>z<sup>*</sup> = argmin<sub>z</sub> ‖g(z) − y<sup>*</sup>‖²</dd><dt style="--k:var(--c-candidates)">Decode</dt><dd>x<sup>*</sup> = D(z<sup>*</sup>)</dd><dt style="--k:var(--c-search)">Validate</dt><dd>E<sub>hull</sub>(x<sup>*</sup>) ≤ ε</dd></dl>
|
| 121 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
</section>
|
| 123 |
|
| 124 |
<figure class="video" id="tour">
|
|
@@ -201,6 +223,7 @@ footer{color:var(--faint);font-size:13.5px;padding:10px 0 40px;display:flex;gap:
|
|
| 201 |
document.getElementById('copybib').onclick=async()=>{ const m=document.getElementById('copied'); try{ await navigator.clipboard.writeText(document.getElementById('bib').textContent); m.textContent='copied'; setTimeout(()=>m.textContent='',1500); }catch(e){ m.textContent='select the text below and copy it'; } };
|
| 202 |
})();
|
| 203 |
</script>
|
|
|
|
| 204 |
<script src="/ask-prism.js" defer></script>
|
| 205 |
</body>
|
| 206 |
</html>
|
|
|
|
| 20 |
@font-face{font-family:'STIX Two Text';font-style:normal;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-normal-greek.woff2) format('woff2');unicode-range:U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF}
|
| 21 |
@font-face{font-family:'STIX Two Text';font-style:italic;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-italic-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
| 22 |
@font-face{font-family:'STIX Two Text';font-style:italic;font-weight:400 700;font-display:swap;src:url(/docs/assets/fonts/stixtwotext-italic-greek.woff2) format('woff2');unicode-range:U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF}
|
| 23 |
+
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:500 800;font-display:swap;src:url(/docs/assets/fonts/plusjakartasans-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
| 24 |
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(/docs/assets/fonts/ibmplexmono-normal-latin.woff2) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
|
| 25 |
:root{--c-data:#2a78d6;--c-model:#eb6834;--c-family:#1baf7a;--c-rules:#eda100;--c-targets:#e87ba4;--c-search:#008300;--c-candidates:#4a3aa7;
|
| 26 |
--p1:#4f46e5;--p2:#9333ea;--bg:#f6f7f5;--card:#fff;--ink:#0b0b0b;--muted:#52514e;--faint:#898781;--line:#e1e0d9;
|
| 27 |
--link:#4f46e5;--eyebrow-mix:#000;
|
| 28 |
--serif:'STIX Two Text','Cambria','Sitka Text',Georgia,serif;--math:'STIX Two Text','Cambria Math','STIX Two Math','Latin Modern Math',serif;
|
| 29 |
+
--display:'Plus Jakarta Sans',system-ui,'Segoe UI',sans-serif;--mono:'IBM Plex Mono',ui-monospace,'Cascadia Mono',Consolas,monospace}
|
| 30 |
:root[data-theme="dark"]{--bg:#111417;--card:#1b1f24;--ink:#fff;--muted:#c3c2b7;--faint:#898781;--line:#2c2c2a;--link:#a5b4fc;--eyebrow-mix:#fff}
|
| 31 |
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--bg:#111417;--card:#1b1f24;--ink:#fff;--muted:#c3c2b7;--faint:#898781;--line:#2c2c2a;--link:#a5b4fc;--eyebrow-mix:#fff}}
|
| 32 |
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
|
|
| 38 |
nav{margin-left:auto;display:flex;gap:2px;flex-wrap:wrap}nav a{text-decoration:none;color:var(--muted);padding:6px 9px;border-radius:8px;font-size:14px;font-weight:600;white-space:nowrap}
|
| 39 |
nav a:hover,nav a[aria-current]{background:var(--bg);color:var(--ink)}nav a.cta{background:var(--p1);color:#fff}nav a.cta:hover{background:var(--p2);color:#fff}
|
| 40 |
@media(max-width:1080px){nav{order:3;width:100%;margin-left:0;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding:0 0 8px}nav::-webkit-scrollbar{display:none}header .theme{margin-left:auto}}
|
| 41 |
+
.hero{position:relative;min-height:540px;padding:48px 0 22px;display:flex;align-items:center}
|
| 42 |
+
.hero .copy{position:relative;z-index:2;max-width:640px}
|
| 43 |
+
.hero::before{content:"";position:absolute;z-index:-1;inset:-30px 0 -10px;pointer-events:none;background:radial-gradient(42% 58% at 78% 42%,rgba(99,102,241,.13),transparent 70%),radial-gradient(34% 44% at 62% 82%,rgba(20,184,166,.11),transparent 70%),radial-gradient(30% 40% at 12% 18%,rgba(59,130,246,.07),transparent 70%)}
|
| 44 |
+
@media(max-width:900px){.hero{display:block;min-height:0;padding-top:28px}}
|
| 45 |
.hero>*{min-width:0}
|
| 46 |
+
h1.mh{font:800 clamp(25px,3.05vw,40px)/1.14 var(--display);letter-spacing:-.022em;margin:0 0 22px;color:var(--ink)}
|
| 47 |
+
h1.mh .ln{display:block}h1.mh .for{font-weight:700;letter-spacing:-.01em}
|
| 48 |
+
.mol{background:linear-gradient(90deg,#2f6fe4,#8b5cf6);-webkit-background-clip:text;background-clip:text;color:transparent}
|
| 49 |
+
.mol.mol-on{background:none;color:inherit}.mol.mol-on>span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
| 50 |
+
.mol svg{display:inline-block;height:auto;max-width:100%;overflow:visible}
|
| 51 |
+
.mol-shadow{fill:none;stroke:rgba(30,41,90,.13);stroke-width:.15;stroke-linecap:round;stroke-linejoin:round}
|
| 52 |
+
:root[data-theme="dark"] .mol-shadow{stroke:rgba(0,0,0,.45)}
|
| 53 |
+
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .mol-shadow{stroke:rgba(0,0,0,.45)}}
|
| 54 |
+
.tag{font-size:clamp(16.5px,1.45vw,19px);line-height:1.62;color:var(--muted);margin:0 0 24px;max-width:50ch}
|
| 55 |
.btns{display:flex;gap:10px;flex-wrap:wrap;margin:0 0 16px}
|
| 56 |
.btn{display:inline-flex;align-items:center;gap:8px;border-radius:12px;padding:12px 18px;font-weight:700;text-decoration:none;border:1px solid var(--line);background:var(--card);color:var(--ink);font-size:16px}
|
| 57 |
.btn.primary{background:var(--p1);border-color:var(--p1);color:#fff}.btn.primary:hover{background:var(--p2);border-color:var(--p2)}.btn:hover{box-shadow:0 4px 14px -8px rgba(0,0,0,.5)}
|
| 58 |
.pub{font:15.5px/1.5 var(--serif);color:var(--muted);margin:0}.pub b{color:var(--ink)}
|
| 59 |
+
.scene{position:absolute;z-index:1;right:-36px;top:0;bottom:0;width:56%;margin:0}
|
| 60 |
+
.scene canvas{position:absolute;inset:0;width:100%;height:100%;display:block;cursor:grab;touch-action:pan-y}
|
| 61 |
+
.scene canvas.grabbing{cursor:grabbing}
|
| 62 |
+
.scene figcaption{position:absolute;right:4px;bottom:0;font:500 11.5px/1.3 var(--mono);color:var(--faint);letter-spacing:.02em}
|
| 63 |
+
@media(max-width:900px){.scene{position:relative;right:auto;width:100%;max-width:560px;aspect-ratio:1/.86;margin:-28px auto 0}}
|
| 64 |
+
.method{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr) minmax(0,1.05fr) minmax(0,1.3fr) minmax(0,.8fr) minmax(0,.9fr);gap:0;margin:6px 0 30px;border:1px solid var(--line);border-radius:14px;background:var(--card);overflow:hidden}
|
| 65 |
+
.method div{padding:12px 12px 13px;border-left:1px solid var(--line);position:relative}.method div:first-child{border-left:0}
|
| 66 |
+
.method div:not(:first-child)::before{content:"→";position:absolute;left:-7px;top:11px;font-size:12px;color:var(--faint);background:var(--card);line-height:1}
|
| 67 |
+
.method b{display:block;font:500 10.5px/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;margin-bottom:9px;color:color-mix(in oklab,var(--k) 78%,var(--eyebrow-mix))}
|
| 68 |
+
.method .eq{display:block;font:italic 400 clamp(14.5px,1.15vw,16.5px)/1.35 var(--math);color:var(--ink);white-space:nowrap;overflow-x:auto;scrollbar-width:none}
|
| 69 |
+
.method .eq::-webkit-scrollbar{display:none}.method .up{font-style:normal}.method sub,.method sup{font-size:.68em;line-height:0}
|
| 70 |
+
@media(max-width:1000px){.method{grid-template-columns:repeat(3,minmax(0,1fr))}.method div:nth-child(4){border-left:0}.method div:nth-child(n+4){border-top:1px solid var(--line)}.method div:nth-child(4)::before{display:none}}
|
| 71 |
+
@media(max-width:560px){.method{grid-template-columns:repeat(2,minmax(0,1fr))}.method div:nth-child(odd){border-left:0}.method div:nth-child(4){border-left:1px solid var(--line)}.method div:nth-child(n+3){border-top:1px solid var(--line)}.method div:nth-child(odd)::before{display:none}.method div:nth-child(4)::before{display:block}}
|
| 72 |
.frac{display:inline-flex;flex-direction:column;align-items:center;vertical-align:middle;margin:0 .15em;line-height:1.15}
|
| 73 |
.frac>span:first-child{border-bottom:1px solid currentColor;padding:0 .2em .05em}.frac>span:last-child{padding:.08em .2em 0}
|
|
|
|
|
|
|
| 74 |
.video{margin:12px 0 28px}.video video{width:100%;height:auto;border-radius:14px;border:1px solid var(--line);background:#000;display:block}
|
| 75 |
.video figcaption{font-size:14px;color:var(--muted);margin-top:8px}
|
| 76 |
.video video[hidden]{display:none}
|
|
|
|
| 121 |
<main class="wrap">
|
| 122 |
<section class="hero">
|
| 123 |
<div>
|
| 124 |
+
<div class="copy">
|
| 125 |
+
<h1 class="mh"><span class="ln">Learn, build and benchmark </span><span class="ln"><span class="mol" data-mol="a"><span>multimodal AI</span></span> <span class="for">for</span> </span><span class="ln"><span class="mol" data-mol="b"><span>materials discovery</span></span></span></h1>
|
| 126 |
+
<p class="tag">Go from multimodal learning to working models and reproducible materials-discovery workflows. Explore architectures, train on your own data, compare results, and use the MEIDNet methodology to learn shared representations of materials and their properties.</p>
|
| 127 |
<div class="btns"><a class="btn primary" href="/studio/">▶ Try MEIDNet</a><a class="btn" href="/studio/?panel=data">Use your dataset</a><a class="btn" href="/docs/learn/index.html">Learn multimodality</a></div>
|
| 128 |
<p class="pub">A. Babu <i>et al.</i>, <i>npj Comput. Mater.</i> <b>12</b>, 287 (2026) · <a href="https://doi.org/10.1038/s41524-026-02153-3">Article</a> · <a href="https://github.com/ABnano/MEIDNet">Code</a></p>
|
| 129 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
</div>
|
| 131 |
+
<figure class="scene">
|
| 132 |
+
<canvas id="hero3d" role="img" aria-label="A cubic ABX3 perovskite: eight corner-sharing BX6 octahedra around an A-site cation, with panels for its crystal graph, a band-gap property curve and the shared latent space where structures and properties are paired."></canvas>
|
| 133 |
+
<figcaption>cubic ABX<sub>3</sub> perovskite · drag to rotate</figcaption>
|
| 134 |
+
</figure>
|
| 135 |
+
</section>
|
| 136 |
+
|
| 137 |
+
<section class="method" aria-label="The method in six steps">
|
| 138 |
+
<div style="--k:var(--c-model)"><b>Encode</b><span class="eq">h<sub>i</sub> ← φ<sub>h</sub>(h<sub>i</sub>, Σ<sub>j</sub> m<sub>ij</sub>)</span></div>
|
| 139 |
+
<div style="--k:var(--c-family)"><b>Align</b><span class="eq">ℒ = −log <span class="frac"><span>exp(s<sub>ii</sub>/τ)</span><span>Σ<sub>j</sub> exp(s<sub>ij</sub>/τ)</span></span></span></div>
|
| 140 |
+
<div style="--k:var(--p2)"><b>Fuse</b><span class="eq">z = ½ (z<sub>s</sub> + z<sub>p</sub>) <span class="up">∈ ℝ<sup>128</sup></span></span></div>
|
| 141 |
+
<div style="--k:var(--c-targets)"><b>Design</b><span class="eq">z<sup>*</sup> = argmin<sub>z</sub> ‖g(z) − y<sup>*</sup>‖²</span></div>
|
| 142 |
+
<div style="--k:var(--c-candidates)"><b>Decode</b><span class="eq">x<sup>*</sup> = D(z<sup>*</sup>)</span></div>
|
| 143 |
+
<div style="--k:var(--c-search)"><b>Validate</b><span class="eq">E<sub>hull</sub>(x<sup>*</sup>) ≤ ε</span></div>
|
| 144 |
</section>
|
| 145 |
|
| 146 |
<figure class="video" id="tour">
|
|
|
|
| 223 |
document.getElementById('copybib').onclick=async()=>{ const m=document.getElementById('copied'); try{ await navigator.clipboard.writeText(document.getElementById('bib').textContent); m.textContent='copied'; setTimeout(()=>m.textContent='',1500); }catch(e){ m.textContent='select the text below and copy it'; } };
|
| 224 |
})();
|
| 225 |
</script>
|
| 226 |
+
<script src="/docs/assets/prism-hero.js" defer></script>
|
| 227 |
<script src="/ask-prism.js" defer></script>
|
| 228 |
</body>
|
| 229 |
</html>
|
site/assets/fonts/OFL-PlusJakartaSans.txt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans)
|
| 2 |
+
|
| 3 |
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
| 4 |
+
This license is copied below, and is also available with a FAQ at:
|
| 5 |
+
http://scripts.sil.org/OFL
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
-----------------------------------------------------------
|
| 9 |
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
| 10 |
+
-----------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
PREAMBLE
|
| 13 |
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
| 14 |
+
development of collaborative font projects, to support the font creation
|
| 15 |
+
efforts of academic and linguistic communities, and to provide a free and
|
| 16 |
+
open framework in which fonts may be shared and improved in partnership
|
| 17 |
+
with others.
|
| 18 |
+
|
| 19 |
+
The OFL allows the licensed fonts to be used, studied, modified and
|
| 20 |
+
redistributed freely as long as they are not sold by themselves. The
|
| 21 |
+
fonts, including any derivative works, can be bundled, embedded,
|
| 22 |
+
redistributed and/or sold with any software provided that any reserved
|
| 23 |
+
names are not used by derivative works. The fonts and derivatives,
|
| 24 |
+
however, cannot be released under any other type of license. The
|
| 25 |
+
requirement for fonts to remain under this license does not apply
|
| 26 |
+
to any document created using the fonts or their derivatives.
|
| 27 |
+
|
| 28 |
+
DEFINITIONS
|
| 29 |
+
"Font Software" refers to the set of files released by the Copyright
|
| 30 |
+
Holder(s) under this license and clearly marked as such. This may
|
| 31 |
+
include source files, build scripts and documentation.
|
| 32 |
+
|
| 33 |
+
"Reserved Font Name" refers to any names specified as such after the
|
| 34 |
+
copyright statement(s).
|
| 35 |
+
|
| 36 |
+
"Original Version" refers to the collection of Font Software components as
|
| 37 |
+
distributed by the Copyright Holder(s).
|
| 38 |
+
|
| 39 |
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
| 40 |
+
or substituting -- in part or in whole -- any of the components of the
|
| 41 |
+
Original Version, by changing formats or by porting the Font Software to a
|
| 42 |
+
new environment.
|
| 43 |
+
|
| 44 |
+
"Author" refers to any designer, engineer, programmer, technical
|
| 45 |
+
writer or other person who contributed to the Font Software.
|
| 46 |
+
|
| 47 |
+
PERMISSION & CONDITIONS
|
| 48 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
| 49 |
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
| 50 |
+
redistribute, and sell modified and unmodified copies of the Font
|
| 51 |
+
Software, subject to the following conditions:
|
| 52 |
+
|
| 53 |
+
1) Neither the Font Software nor any of its individual components,
|
| 54 |
+
in Original or Modified Versions, may be sold by itself.
|
| 55 |
+
|
| 56 |
+
2) Original or Modified Versions of the Font Software may be bundled,
|
| 57 |
+
redistributed and/or sold with any software, provided that each copy
|
| 58 |
+
contains the above copyright notice and this license. These can be
|
| 59 |
+
included either as stand-alone text files, human-readable headers or
|
| 60 |
+
in the appropriate machine-readable metadata fields within text or
|
| 61 |
+
binary files as long as those fields can be easily viewed by the user.
|
| 62 |
+
|
| 63 |
+
3) No Modified Version of the Font Software may use the Reserved Font
|
| 64 |
+
Name(s) unless explicit written permission is granted by the corresponding
|
| 65 |
+
Copyright Holder. This restriction only applies to the primary font name as
|
| 66 |
+
presented to the users.
|
| 67 |
+
|
| 68 |
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
| 69 |
+
Software shall not be used to promote, endorse or advertise any
|
| 70 |
+
Modified Version, except to acknowledge the contribution(s) of the
|
| 71 |
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
| 72 |
+
permission.
|
| 73 |
+
|
| 74 |
+
5) The Font Software, modified or unmodified, in part or in whole,
|
| 75 |
+
must be distributed entirely under this license, and must not be
|
| 76 |
+
distributed under any other license. The requirement for fonts to
|
| 77 |
+
remain under this license does not apply to any document created
|
| 78 |
+
using the Font Software.
|
| 79 |
+
|
| 80 |
+
TERMINATION
|
| 81 |
+
This license becomes null and void if any of the above conditions are
|
| 82 |
+
not met.
|
| 83 |
+
|
| 84 |
+
DISCLAIMER
|
| 85 |
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 86 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
| 87 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
| 88 |
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
| 89 |
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
| 90 |
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
| 91 |
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
| 92 |
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
| 93 |
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
site/assets/fonts/plusjakartasans-latin.woff2
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:153fc85b70298beeb1d61a5f723331649e7f23bb77302a66e61cb3e2fbdb5e79
|
| 3 |
+
size 27348
|
site/assets/prism-hero.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* MEIDNet Prism landing page: the molecular lettering of the headline and the 3D crystal scene of the hero.
|
| 2 |
+
No libraries: the lettering is SVG built from stroke glyphs, the scene a small canvas renderer (painter's algorithm).
|
| 3 |
+
The headline stays real text for search engines and screen readers; without JavaScript it shows as plain text. */
|
| 4 |
+
(function () {
|
| 5 |
+
"use strict";
|
| 6 |
+
var NS = "http://www.w3.org/2000/svg";
|
| 7 |
+
|
| 8 |
+
function isDark() {
|
| 9 |
+
var t = document.documentElement.getAttribute("data-theme");
|
| 10 |
+
if (t) return t === "dark";
|
| 11 |
+
return !!(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
/* ═══════════ molecular lettering: bonds along the strokes, atoms at the joints ═══════════ */
|
| 15 |
+
var ASC = 1.42, DESC = 0.48, PAD = 0.24, GAP = 0.36, U = 0.76; // x-height = 1 unit; U = unit in em
|
| 16 |
+
|
| 17 |
+
function arc(cx, cy, rx, ry, a0, a1, every) {
|
| 18 |
+
var n = Math.max(8, Math.ceil(Math.abs(a1 - a0) / 6)), pts = [], beads = [];
|
| 19 |
+
for (var i = 0; i <= n; i++) {
|
| 20 |
+
var a = (a0 + (a1 - a0) * i / n) * Math.PI / 180;
|
| 21 |
+
pts.push([cx + rx * Math.cos(a), cy + ry * Math.sin(a)]);
|
| 22 |
+
}
|
| 23 |
+
var k = Math.max(1, Math.round(n * every / Math.abs(a1 - a0)));
|
| 24 |
+
for (var j = 0; j <= n; j += k) beads.push(pts[j]);
|
| 25 |
+
beads.push(pts[n]);
|
| 26 |
+
return {pts: pts, beads: beads};
|
| 27 |
+
}
|
| 28 |
+
function line() { var p = Array.prototype.slice.call(arguments); return {pts: p, beads: p}; }
|
| 29 |
+
function dot(x, y) { return {pts: [], beads: [[x, y]]}; }
|
| 30 |
+
var ring = function () { return arc(0.5, 0.5, 0.5, 0.5, 0, 360, 72); };
|
| 31 |
+
|
| 32 |
+
var G = {
|
| 33 |
+
a: {w: 1.0, s: function () { return [ring(), line([1, 1], [1, 0])]; }},
|
| 34 |
+
c: {w: 0.92, s: function () { return [arc(0.5, 0.5, 0.5, 0.5, 40, 320, 70)]; }},
|
| 35 |
+
d: {w: 1.0, s: function () { return [ring(), line([1, ASC], [1, 0])]; }},
|
| 36 |
+
e: {w: 1.0, s: function () { return [line([0.02, 0.5], [1, 0.5]), arc(0.5, 0.5, 0.5, 0.5, 0, 318, 80)]; }},
|
| 37 |
+
i: {w: 0.2, s: function () { return [line([0.1, 0], [0.1, 1]), dot(0.1, 1.34)]; }},
|
| 38 |
+
l: {w: 0.2, s: function () { return [line([0.1, 0], [0.1, 0.71], [0.1, ASC])]; }},
|
| 39 |
+
m: {w: 1.3, s: function () {
|
| 40 |
+
return [line([0, 0], [0, 1]), arc(0.325, 0.62, 0.325, 0.38, 180, 0, 90), line([0.65, 0.62], [0.65, 0]),
|
| 41 |
+
arc(0.975, 0.62, 0.325, 0.38, 180, 0, 90), line([1.3, 0.62], [1.3, 0])]; }},
|
| 42 |
+
o: {w: 1.0, s: function () { return [arc(0.5, 0.5, 0.5, 0.5, 90, 450, 60)]; }},
|
| 43 |
+
r: {w: 0.68, s: function () { return [line([0, 0], [0, 1]), arc(0.42, 0.55, 0.42, 0.42, 180, 60, 60)]; }},
|
| 44 |
+
s: {w: 0.82, s: function () { return [arc(0.41, 0.75, 0.36, 0.25, 20, 270, 85), arc(0.41, 0.25, 0.39, 0.25, 90, -160, 85)]; }},
|
| 45 |
+
t: {w: 0.72, s: function () { return [line([0.28, 1.32], [0.28, 1], [0.28, 0]), line([0, 1], [0.66, 1])]; }},
|
| 46 |
+
u: {w: 0.92, s: function () { return [line([0, 1], [0, 0.46]), arc(0.46, 0.46, 0.46, 0.46, 180, 360, 90), line([0.92, 1], [0.92, 0])]; }},
|
| 47 |
+
v: {w: 0.92, s: function () { return [line([0, 1], [0.46, 0], [0.92, 1])]; }},
|
| 48 |
+
y: {w: 0.92, s: function () { return [line([0, 1], [0.46, 0]), line([0.92, 1], [0.46, 0], [0.24, -0.48])]; }},
|
| 49 |
+
A: {w: 1.16, s: function () { return [line([0, 0], [0.58, ASC], [1.16, 0]), line([0.25, 0.55], [0.91, 0.55])]; }},
|
| 50 |
+
I: {w: 0.2, s: function () { return [line([0.1, 0], [0.1, 0.71], [0.1, ASC])]; }},
|
| 51 |
+
" ": {w: 0.38, s: function () { return []; }}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
var lettering = 0;
|
| 55 |
+
function moleculeSvg(text, stops) {
|
| 56 |
+
var id = "mol" + (++lettering), x = PAD, paths = [], beads = [];
|
| 57 |
+
for (var c = 0; c < text.length; c++) {
|
| 58 |
+
var g = G[text[c]];
|
| 59 |
+
if (!g) return null; // a glyph we do not draw: keep the plain text
|
| 60 |
+
g.s().forEach(function (st) {
|
| 61 |
+
if (st.pts.length) paths.push(st.pts.map(function (p) { return [p[0] + x, p[1]]; }));
|
| 62 |
+
st.beads.forEach(function (b) {
|
| 63 |
+
var q = [b[0] + x, b[1]];
|
| 64 |
+
if (!beads.some(function (o) { return Math.hypot(o[0] - q[0], o[1] - q[1]) < 0.06; })) beads.push(q);
|
| 65 |
+
});
|
| 66 |
+
});
|
| 67 |
+
x += g.w + GAP;
|
| 68 |
+
}
|
| 69 |
+
var W = x - GAP + PAD, H = ASC + DESC + 2 * PAD;
|
| 70 |
+
var Y = function (y) { return (ASC + PAD - y).toFixed(3); };
|
| 71 |
+
var d = paths.map(function (p) { return "M" + p.map(function (q) { return q[0].toFixed(3) + " " + Y(q[1]); }).join("L"); }).join("");
|
| 72 |
+
var svg = document.createElementNS(NS, "svg");
|
| 73 |
+
svg.setAttribute("viewBox", "0 0 " + W.toFixed(3) + " " + H.toFixed(3));
|
| 74 |
+
svg.setAttribute("aria-hidden", "true");
|
| 75 |
+
svg.setAttribute("focusable", "false");
|
| 76 |
+
svg.style.width = (W * U).toFixed(3) + "em";
|
| 77 |
+
svg.style.verticalAlign = (-(DESC + PAD) * U).toFixed(3) + "em";
|
| 78 |
+
svg.innerHTML =
|
| 79 |
+
'<defs><linearGradient id="' + id + 'g" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="' + W.toFixed(2) + '" y2="0">' +
|
| 80 |
+
stops.map(function (s) { return '<stop offset="' + s[0] + '" stop-color="' + s[1] + '"/>'; }).join("") + "</linearGradient>" +
|
| 81 |
+
'<radialGradient id="' + id + 's" cx=".36" cy=".32" r=".68"><stop offset="0" stop-color="#fff" stop-opacity=".95"/>' +
|
| 82 |
+
'<stop offset=".38" stop-color="#fff" stop-opacity=".28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient></defs>' +
|
| 83 |
+
'<path class="mol-shadow" d="' + d + '" transform="translate(.05 .07)"/>' +
|
| 84 |
+
'<path d="' + d + '" fill="none" stroke="url(#' + id + 'g)" stroke-width=".2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
| 85 |
+
'<path d="' + d + '" fill="none" stroke="#fff" stroke-opacity=".45" stroke-width=".05" stroke-linecap="round" stroke-linejoin="round" transform="translate(-.03 -.04)"/>' +
|
| 86 |
+
beads.map(function (b) {
|
| 87 |
+
var cx = b[0].toFixed(3), cy = Y(b[1]);
|
| 88 |
+
return '<circle cx="' + cx + '" cy="' + cy + '" r=".158" fill="url(#' + id + 'g)"/>' +
|
| 89 |
+
'<circle cx="' + cx + '" cy="' + cy + '" r=".158" fill="url(#' + id + 's)"/>';
|
| 90 |
+
}).join("");
|
| 91 |
+
return svg;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
var PALETTES = {
|
| 95 |
+
a: [[0, "#2f6fe4"], [0.45, "#6366f1"], [0.8, "#8b5cf6"], [1, "#a855f7"]],
|
| 96 |
+
b: [[0, "#2563eb"], [0.3, "#0e7490"], [0.55, "#0d9488"], [1, "#10b981"]]
|
| 97 |
+
};
|
| 98 |
+
document.querySelectorAll("[data-mol]").forEach(function (span) {
|
| 99 |
+
var svg = moleculeSvg(span.textContent.trim(), PALETTES[span.getAttribute("data-mol")] || PALETTES.a);
|
| 100 |
+
if (!svg) return;
|
| 101 |
+
span.classList.add("mol-on");
|
| 102 |
+
span.insertBefore(svg, span.firstChild);
|
| 103 |
+
});
|
| 104 |
+
|
| 105 |
+
/* ═══════════ the 3D scene: an ABX3 perovskite and what MEIDNet makes of it ═══════════ */
|
| 106 |
+
var canvas = document.getElementById("hero3d");
|
| 107 |
+
if (!canvas || !canvas.getContext) return;
|
| 108 |
+
var ctx = canvas.getContext("2d");
|
| 109 |
+
var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 110 |
+
|
| 111 |
+
// crystal: the cubic ABX3 cell, A cations on the corners, the BX6 octahedron in the centre (X on the face centres)
|
| 112 |
+
var a = 1.25, B = [[0, 0, 0]], A = [], EDGES = [], FACES = [];
|
| 113 |
+
var X = [[a, 0, 0], [-a, 0, 0], [0, a, 0], [0, -a, 0], [0, 0, a], [0, 0, -a]];
|
| 114 |
+
[-a, a].forEach(function (x) { [-a, a].forEach(function (y) { [-a, a].forEach(function (z) { A.push([x, y, z]); }); }); });
|
| 115 |
+
A.forEach(function (p) { A.forEach(function (q) { // the cube: corners one lattice step apart
|
| 116 |
+
var d = Math.abs(p[0] - q[0]) + Math.abs(p[1] - q[1]) + Math.abs(p[2] - q[2]);
|
| 117 |
+
if (Math.abs(d - 2 * a) < 1e-6 && p < q) EDGES.push([p, q]);
|
| 118 |
+
}); });
|
| 119 |
+
[[0, 2, 4], [0, 4, 3], [0, 3, 5], [0, 5, 2], [1, 4, 2], [1, 3, 4], [1, 5, 3], [1, 2, 5]].forEach(function (f) {
|
| 120 |
+
FACES.push([X[f[0]], X[f[1]], X[f[2]]]);
|
| 121 |
+
});
|
| 122 |
+
|
| 123 |
+
// panels behind the crystal: centre, half sizes, rotation about y, what they show
|
| 124 |
+
function rng(seed) { return function () { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; }; }
|
| 125 |
+
var r = rng(7), LATENT = [];
|
| 126 |
+
for (var i = 0; i < 90; i++) { // paired points: a structure (teal) and its properties (violet), close together
|
| 127 |
+
var t = r() * 2 - 1, u = r() * 2 - 1, cx = 0.75 * t, cy = 0.55 * Math.sin(2.2 * t) * 0.8 + 0.25 * u;
|
| 128 |
+
LATENT.push({u: cx, v: cy, du: (r() - 0.5) * 0.09, dv: (r() - 0.5) * 0.09, c: t});
|
| 129 |
+
}
|
| 130 |
+
var GRAPH = {n: [[-0.62, -0.35], [-0.22, 0.42], [0.18, -0.12], [0.6, 0.38], [0.52, -0.5], [-0.05, -0.62], [-0.68, 0.38]],
|
| 131 |
+
e: [[0, 1], [1, 2], [2, 3], [2, 4], [0, 5], [5, 2], [1, 6], [6, 0], [3, 4]]};
|
| 132 |
+
var PANELS = [
|
| 133 |
+
{c: [-2.2, 1.5, -2.4], h: [0.95, 0.68], ry: 0.5, kind: "graph", label: "crystal graph"},
|
| 134 |
+
{c: [1.45, 2.0, -2.9], h: [1.15, 0.75], ry: -0.32, kind: "latent", label: "latent space z ∈ ℝ¹²⁸"},
|
| 135 |
+
{c: [2.25, -1.25, -1.6], h: [0.8, 0.56], ry: -0.62, kind: "property", label: "property: band gap"}
|
| 136 |
+
];
|
| 137 |
+
|
| 138 |
+
var W = 0, H = 0, DPR = 1, yaw = 0, pitch = 0, dragYaw = 0, dragPitch = 0, t0 = performance.now();
|
| 139 |
+
function resize() {
|
| 140 |
+
var b = canvas.getBoundingClientRect();
|
| 141 |
+
DPR = Math.min(2, window.devicePixelRatio || 1);
|
| 142 |
+
W = Math.max(1, b.width); H = Math.max(1, b.height);
|
| 143 |
+
canvas.width = Math.round(W * DPR); canvas.height = Math.round(H * DPR);
|
| 144 |
+
draw();
|
| 145 |
+
}
|
| 146 |
+
function proj(p) {
|
| 147 |
+
var cy = Math.cos(yaw), sy = Math.sin(yaw), cp = Math.cos(pitch), sp = Math.sin(pitch);
|
| 148 |
+
var x1 = p[0] * cy + p[2] * sy, z1 = -p[0] * sy + p[2] * cy, y1 = p[1];
|
| 149 |
+
var y2 = y1 * cp - z1 * sp, z2 = y1 * sp + z1 * cp;
|
| 150 |
+
var D = 9, F = Math.min(W, H * 1.1) * D / 8.3, k = F / (D - z2);
|
| 151 |
+
return [W * 0.52 + x1 * k, H * 0.5 - y2 * k, z2, k];
|
| 152 |
+
}
|
| 153 |
+
function panelPoint(P, u, v, w) { // panel-local (u, v in [-1, 1]) to world
|
| 154 |
+
var c = Math.cos(P.ry), sn = Math.sin(P.ry), x = u * P.h[0], y = v * P.h[1];
|
| 155 |
+
return [P.c[0] + x * c + (w || 0) * sn, P.c[1] + y, P.c[2] - x * sn + (w || 0) * c];
|
| 156 |
+
}
|
| 157 |
+
function poly(pts) { ctx.beginPath(); pts.forEach(function (p, j) { j ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1]); }); ctx.closePath(); }
|
| 158 |
+
|
| 159 |
+
function sphere(p, rad, col, dark) {
|
| 160 |
+
var q = proj(p), R = rad * q[3];
|
| 161 |
+
var g = ctx.createRadialGradient(q[0] - R * 0.38, q[1] - R * 0.42, R * 0.08, q[0], q[1], R);
|
| 162 |
+
g.addColorStop(0, "#ffffff"); g.addColorStop(0.22, col[0]); g.addColorStop(0.72, col[1]); g.addColorStop(1, col[2]);
|
| 163 |
+
ctx.beginPath(); ctx.arc(q[0], q[1], R, 0, 2 * Math.PI);
|
| 164 |
+
ctx.fillStyle = g; ctx.fill();
|
| 165 |
+
ctx.lineWidth = 0.8; ctx.strokeStyle = dark ? "rgba(0,0,0,.35)" : "rgba(15,23,42,.12)"; ctx.stroke();
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
function drawPanel(P, dark) {
|
| 169 |
+
var corners = [[-1, -1], [1, -1], [1, 1], [-1, 1]].map(function (c) { return proj(panelPoint(P, c[0], c[1])); });
|
| 170 |
+
poly(corners);
|
| 171 |
+
var g = ctx.createLinearGradient(corners[3][0], corners[3][1], corners[1][0], corners[1][1]);
|
| 172 |
+
g.addColorStop(0, dark ? "rgba(51,65,85,.55)" : "rgba(255,255,255,.78)");
|
| 173 |
+
g.addColorStop(1, dark ? "rgba(30,41,59,.28)" : "rgba(226,232,255,.42)");
|
| 174 |
+
ctx.fillStyle = g; ctx.fill();
|
| 175 |
+
ctx.lineWidth = 1; ctx.strokeStyle = dark ? "rgba(148,163,184,.45)" : "rgba(148,163,184,.55)"; ctx.stroke();
|
| 176 |
+
var L = function (u, v) { return proj(panelPoint(P, u, v)); };
|
| 177 |
+
if (P.kind === "graph") {
|
| 178 |
+
ctx.lineWidth = 1.4; ctx.strokeStyle = dark ? "rgba(148,163,184,.75)" : "rgba(100,116,139,.6)";
|
| 179 |
+
GRAPH.e.forEach(function (e) { var a = L(GRAPH.n[e[0]][0] * 0.8, GRAPH.n[e[0]][1] * 0.8), b = L(GRAPH.n[e[1]][0] * 0.8, GRAPH.n[e[1]][1] * 0.8);
|
| 180 |
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); });
|
| 181 |
+
GRAPH.n.forEach(function (n, j) { var q = L(n[0] * 0.8, n[1] * 0.8);
|
| 182 |
+
ctx.beginPath(); ctx.arc(q[0], q[1], Math.max(2.5, 0.07 * q[3]), 0, 2 * Math.PI);
|
| 183 |
+
ctx.fillStyle = ["#3b82f6", "#14b8a6", "#8b5cf6"][j % 3]; ctx.fill(); });
|
| 184 |
+
} else if (P.kind === "latent") {
|
| 185 |
+
LATENT.forEach(function (p) {
|
| 186 |
+
var a = L(p.u, p.v), b = L(p.u + p.du, p.v + p.dv), rad = Math.max(1.4, 0.028 * a[3]);
|
| 187 |
+
ctx.strokeStyle = dark ? "rgba(203,213,225,.3)" : "rgba(100,116,139,.28)"; ctx.lineWidth = 0.8;
|
| 188 |
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
| 189 |
+
var h = 175 + (p.c + 1) * 50;
|
| 190 |
+
ctx.fillStyle = "hsla(" + h + ",70%," + (dark ? 62 : 48) + "%,.9)";
|
| 191 |
+
ctx.beginPath(); ctx.arc(a[0], a[1], rad, 0, 2 * Math.PI); ctx.fill();
|
| 192 |
+
ctx.fillStyle = "hsla(" + (h + 80) + ",70%," + (dark ? 70 : 58) + "%,.85)";
|
| 193 |
+
ctx.beginPath(); ctx.arc(b[0], b[1], rad * 0.85, 0, 2 * Math.PI); ctx.fill();
|
| 194 |
+
});
|
| 195 |
+
} else {
|
| 196 |
+
ctx.strokeStyle = dark ? "rgba(148,163,184,.6)" : "rgba(100,116,139,.55)"; ctx.lineWidth = 1;
|
| 197 |
+
var o = L(-0.85, -0.7), xe = L(0.85, -0.7), ye = L(-0.85, 0.75);
|
| 198 |
+
ctx.beginPath(); ctx.moveTo(ye[0], ye[1]); ctx.lineTo(o[0], o[1]); ctx.lineTo(xe[0], xe[1]); ctx.stroke();
|
| 199 |
+
ctx.beginPath();
|
| 200 |
+
for (var j = 0; j <= 60; j++) {
|
| 201 |
+
var u = -0.85 + 1.7 * j / 60, xg = (u + 0.85) * 3, // band gap axis, 0 to ~5 eV
|
| 202 |
+
yv = Math.exp(-Math.pow((xg - 1.6) / 0.6, 2)) + 0.55 * Math.exp(-Math.pow((xg - 3.4) / 0.8, 2)),
|
| 203 |
+
q = L(u, -0.7 + 1.3 * yv);
|
| 204 |
+
j ? ctx.lineTo(q[0], q[1]) : ctx.moveTo(q[0], q[1]);
|
| 205 |
+
}
|
| 206 |
+
ctx.strokeStyle = "#8b5cf6"; ctx.lineWidth = 2; ctx.stroke();
|
| 207 |
+
var tu = -0.85 + 2.0 / 3, a = L(tu, -0.7), b = L(tu, 0.7); // the target, 2 eV
|
| 208 |
+
ctx.setLineDash([4, 3]); ctx.strokeStyle = "#e87ba4"; ctx.lineWidth = 1.4;
|
| 209 |
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); ctx.setLineDash([]);
|
| 210 |
+
}
|
| 211 |
+
var tl = corners[3];
|
| 212 |
+
ctx.font = "500 " + Math.round(Math.max(10, Math.min(12.5, W / 46))) + "px 'IBM Plex Mono', ui-monospace, monospace";
|
| 213 |
+
ctx.fillStyle = dark ? "rgba(203,213,225,.85)" : "rgba(71,85,105,.9)";
|
| 214 |
+
ctx.fillText(P.label, Math.max(4, Math.min(tl[0] + 2, W - ctx.measureText(P.label).width - 4)), tl[1] - 7); // inside the canvas
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
function draw() {
|
| 218 |
+
if (!W) return;
|
| 219 |
+
var dark = isDark();
|
| 220 |
+
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
|
| 221 |
+
ctx.clearRect(0, 0, W, H);
|
| 222 |
+
// floor grid
|
| 223 |
+
ctx.lineWidth = 1; ctx.strokeStyle = dark ? "rgba(148,163,184,.12)" : "rgba(100,116,139,.14)";
|
| 224 |
+
for (var g = -3; g <= 3; g += 0.75) {
|
| 225 |
+
var a = proj([g, -2.05, -3]), b = proj([g, -2.05, 3]), c = proj([-3, -2.05, g]), d = proj([3, -2.05, g]);
|
| 226 |
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.moveTo(c[0], c[1]); ctx.lineTo(d[0], d[1]); ctx.stroke();
|
| 227 |
+
}
|
| 228 |
+
// the cation's shadow on the floor
|
| 229 |
+
var f = proj([0, -2.05, 0]), sh = ctx.createRadialGradient(f[0], f[1], 0, f[0], f[1], 2.0 * f[3]);
|
| 230 |
+
sh.addColorStop(0, dark ? "rgba(0,0,0,.35)" : "rgba(30,41,90,.07)"); sh.addColorStop(1, "rgba(0,0,0,0)");
|
| 231 |
+
ctx.fillStyle = sh; ctx.beginPath(); ctx.ellipse(f[0], f[1], 2.0 * f[3], 0.62 * f[3], 0, 0, 2 * Math.PI); ctx.fill();
|
| 232 |
+
// panels, far first
|
| 233 |
+
PANELS.slice().sort(function (p, q) { return proj(p.c)[2] - proj(q.c)[2]; }).forEach(function (P) { drawPanel(P, dark); });
|
| 234 |
+
// crystal: faces and atoms sorted by depth
|
| 235 |
+
var items = [];
|
| 236 |
+
FACES.forEach(function (fc) {
|
| 237 |
+
var q = fc.map(proj), z = (q[0][2] + q[1][2] + q[2][2]) / 3;
|
| 238 |
+
items.push({z: z, f: fc, q: q});
|
| 239 |
+
});
|
| 240 |
+
B.forEach(function (p) { items.push({z: proj(p)[2], p: p, rad: 0.27, col: ["#93c5fd", "#3b82f6", "#1e3a8a"]}); });
|
| 241 |
+
X.forEach(function (p) { items.push({z: proj(p)[2], p: p, rad: 0.2, col: ["#99f6e4", "#14b8a6", "#115e59"]}); });
|
| 242 |
+
A.forEach(function (p) { items.push({z: proj(p)[2], p: p, rad: 0.25, col: ["#c4b5fd", "#8b5cf6", "#3b0764"]}); });
|
| 243 |
+
EDGES.forEach(function (e) { var q = [proj(e[0]), proj(e[1])]; items.push({z: (q[0][2] + q[1][2]) / 2 - 0.4, line: q}); });
|
| 244 |
+
items.sort(function (m, n) { return m.z - n.z; });
|
| 245 |
+
var Ldir = [-0.45, 0.7, 0.55];
|
| 246 |
+
items.forEach(function (it) {
|
| 247 |
+
if (it.p) return sphere(it.p, it.rad, it.col, dark);
|
| 248 |
+
if (it.line) {
|
| 249 |
+
ctx.lineWidth = 1.2; ctx.strokeStyle = dark ? "rgba(148,163,184,.45)" : "rgba(100,116,139,.45)";
|
| 250 |
+
ctx.beginPath(); ctx.moveTo(it.line[0][0], it.line[0][1]); ctx.lineTo(it.line[1][0], it.line[1][1]); ctx.stroke(); return;
|
| 251 |
+
}
|
| 252 |
+
var v = it.f, e1 = [v[1][0] - v[0][0], v[1][1] - v[0][1], v[1][2] - v[0][2]], e2 = [v[2][0] - v[0][0], v[2][1] - v[0][1], v[2][2] - v[0][2]];
|
| 253 |
+
var n = [e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]], nl = Math.hypot(n[0], n[1], n[2]);
|
| 254 |
+
var cy = Math.cos(yaw), sy = Math.sin(yaw), nx = (n[0] * cy + n[2] * sy) / nl, nz = (-n[0] * sy + n[2] * cy) / nl, ny = n[1] / nl;
|
| 255 |
+
var lit = Math.abs(nx * Ldir[0] + ny * Ldir[1] + nz * Ldir[2]);
|
| 256 |
+
poly(it.q);
|
| 257 |
+
var gr = ctx.createLinearGradient(it.q[0][0], it.q[0][1], it.q[2][0], it.q[2][1]);
|
| 258 |
+
gr.addColorStop(0, dark ? "rgba(147,197,253," + (0.1 + 0.22 * lit).toFixed(3) + ")" : "rgba(255,255,255," + (0.25 + 0.45 * lit).toFixed(3) + ")");
|
| 259 |
+
gr.addColorStop(1, dark ? "rgba(59,130,246," + (0.08 + 0.14 * lit).toFixed(3) + ")" : "rgba(129,161,250," + (0.12 + 0.22 * lit).toFixed(3) + ")");
|
| 260 |
+
ctx.fillStyle = gr; ctx.fill();
|
| 261 |
+
ctx.lineWidth = 1; ctx.strokeStyle = dark ? "rgba(191,219,254,.5)" : "rgba(255,255,255,.95)"; ctx.stroke();
|
| 262 |
+
ctx.lineWidth = 0.6; ctx.strokeStyle = dark ? "rgba(96,165,250,.35)" : "rgba(99,102,241,.25)"; ctx.stroke();
|
| 263 |
+
});
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
var running = false, visible = true;
|
| 267 |
+
function frame(now) {
|
| 268 |
+
var t = (now - t0) / 1000;
|
| 269 |
+
yaw = -0.5 + 0.38 * Math.sin(t * 0.21) + dragYaw;
|
| 270 |
+
pitch = -0.33 + 0.05 * Math.sin(t * 0.16) + dragPitch;
|
| 271 |
+
draw();
|
| 272 |
+
if (running) requestAnimationFrame(frame);
|
| 273 |
+
}
|
| 274 |
+
function start() { if (reduce || running || !visible || document.hidden) return; running = true; requestAnimationFrame(frame); }
|
| 275 |
+
function stop() { running = false; }
|
| 276 |
+
if (reduce) { yaw = -0.5; pitch = -0.33; }
|
| 277 |
+
|
| 278 |
+
var drag = null;
|
| 279 |
+
canvas.addEventListener("pointerdown", function (e) { drag = {x: e.clientX, y: e.clientY, yaw: dragYaw, pitch: dragPitch}; canvas.setPointerCapture(e.pointerId); canvas.classList.add("grabbing"); });
|
| 280 |
+
canvas.addEventListener("pointermove", function (e) {
|
| 281 |
+
if (!drag) return;
|
| 282 |
+
dragYaw = drag.yaw + (e.clientX - drag.x) * 0.008;
|
| 283 |
+
dragPitch = Math.max(-0.5, Math.min(0.45, drag.pitch + (e.clientY - drag.y) * 0.005));
|
| 284 |
+
if (reduce) { yaw = -0.5 + dragYaw; pitch = -0.33 + dragPitch; draw(); }
|
| 285 |
+
});
|
| 286 |
+
function endDrag() { drag = null; canvas.classList.remove("grabbing"); }
|
| 287 |
+
canvas.addEventListener("pointerup", endDrag); canvas.addEventListener("pointercancel", endDrag);
|
| 288 |
+
|
| 289 |
+
if (window.ResizeObserver) new ResizeObserver(resize).observe(canvas); else window.addEventListener("resize", resize);
|
| 290 |
+
if (window.IntersectionObserver) new IntersectionObserver(function (es) { visible = es[0].isIntersecting; visible ? start() : stop(); }).observe(canvas);
|
| 291 |
+
document.addEventListener("visibilitychange", function () { document.hidden ? stop() : start(); });
|
| 292 |
+
var th = document.getElementById("theme");
|
| 293 |
+
if (th) th.addEventListener("click", function () { setTimeout(draw, 0); });
|
| 294 |
+
resize();
|
| 295 |
+
start();
|
| 296 |
+
})();
|