mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-16 14:42:29 -05:00
1909 lines
56 KiB
HTML
1909 lines
56 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>MLSysBook Lab Belts And Knobs Catalog</title>
|
|
<style>
|
|
:root {
|
|
color-scheme: light;
|
|
--ink: #172033;
|
|
--muted: #596579;
|
|
--line: #d8dee9;
|
|
--panel: #ffffff;
|
|
--soft: #f5f7fb;
|
|
--wash: #eef2f7;
|
|
--nav: #172033;
|
|
--blue: #006395;
|
|
--green: #087f5b;
|
|
--amber: #b85c00;
|
|
--red: #c33149;
|
|
--purple: #6254b2;
|
|
--teal: #007c89;
|
|
--radius: 8px;
|
|
--shadow: 0 1px 3px rgba(23, 32, 51, 0.08);
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
color: var(--ink);
|
|
background: var(--wash);
|
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
a {
|
|
color: inherit;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
select {
|
|
font: inherit;
|
|
}
|
|
|
|
.shell {
|
|
min-height: 100vh;
|
|
display: grid;
|
|
grid-template-columns: 280px minmax(0, 1fr);
|
|
}
|
|
|
|
.sidebar {
|
|
position: sticky;
|
|
top: 0;
|
|
height: 100vh;
|
|
overflow: auto;
|
|
padding: 18px 16px 24px;
|
|
background: var(--nav);
|
|
color: #f8fafc;
|
|
border-right: 1px solid #111827;
|
|
}
|
|
|
|
.brand {
|
|
display: grid;
|
|
grid-template-columns: 38px minmax(0, 1fr);
|
|
gap: 10px;
|
|
align-items: center;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.brand img {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 6px;
|
|
background: #ffffff;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.brand strong {
|
|
display: block;
|
|
font-size: 0.98rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.brand span {
|
|
display: block;
|
|
margin-top: 2px;
|
|
color: #cbd5e1;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.nav-group {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.nav-title {
|
|
margin: 0 0 8px;
|
|
color: #a9b6c9;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
.nav-links {
|
|
display: grid;
|
|
gap: 5px;
|
|
}
|
|
|
|
.nav-links a {
|
|
display: block;
|
|
padding: 8px 10px;
|
|
color: #edf2f7;
|
|
text-decoration: none;
|
|
border-radius: var(--radius);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.nav-links a:hover,
|
|
.nav-links a:focus-visible {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
outline: none;
|
|
}
|
|
|
|
.sidebar-note {
|
|
margin-top: 18px;
|
|
padding: 12px;
|
|
color: #dbe6f3;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
border-radius: var(--radius);
|
|
font-size: 0.84rem;
|
|
}
|
|
|
|
main {
|
|
min-width: 0;
|
|
padding: 28px;
|
|
}
|
|
|
|
.hero {
|
|
padding: 24px;
|
|
margin-bottom: 18px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.eyebrow {
|
|
margin: 0 0 8px;
|
|
color: var(--blue);
|
|
font-size: 0.78rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
p {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
h1 {
|
|
max-width: 920px;
|
|
margin: 0;
|
|
font-size: clamp(1.8rem, 3vw, 3rem);
|
|
line-height: 1.08;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.lead {
|
|
max-width: 980px;
|
|
margin: 12px 0 0;
|
|
color: var(--muted);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.hero-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 12px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.metric {
|
|
min-height: 92px;
|
|
padding: 13px;
|
|
background: var(--soft);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.metric strong {
|
|
display: block;
|
|
font-size: 1.52rem;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.metric span {
|
|
display: block;
|
|
margin-top: 6px;
|
|
color: var(--muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
section {
|
|
margin-bottom: 18px;
|
|
padding: 20px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
section h2 {
|
|
margin: 0 0 8px;
|
|
font-size: 1.18rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.section-note {
|
|
max-width: 980px;
|
|
margin: 0 0 16px;
|
|
color: var(--muted);
|
|
font-size: 0.93rem;
|
|
}
|
|
|
|
.workflow {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.flow-step {
|
|
position: relative;
|
|
min-height: 130px;
|
|
padding: 14px;
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-top: 4px solid var(--blue);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.flow-step:nth-child(2) {
|
|
border-top-color: var(--green);
|
|
}
|
|
|
|
.flow-step:nth-child(3) {
|
|
border-top-color: var(--amber);
|
|
}
|
|
|
|
.flow-step:nth-child(4) {
|
|
border-top-color: var(--purple);
|
|
}
|
|
|
|
.flow-step .step-index {
|
|
color: var(--muted);
|
|
font-size: 0.72rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
.flow-step h3 {
|
|
margin: 7px 0 7px;
|
|
font-size: 1rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.flow-step p {
|
|
margin: 0;
|
|
color: var(--muted);
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.track-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.track-card {
|
|
min-height: 210px;
|
|
padding: 14px;
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.track-card h3 {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin: 0 0 8px;
|
|
font-size: 1rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.category {
|
|
display: inline-block;
|
|
padding: 3px 7px;
|
|
color: #ffffff;
|
|
background: var(--blue);
|
|
border-radius: 999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.track-card:nth-child(2) .category {
|
|
background: var(--green);
|
|
}
|
|
|
|
.track-card:nth-child(3) .category {
|
|
background: var(--amber);
|
|
}
|
|
|
|
.track-card:nth-child(4) .category {
|
|
background: var(--purple);
|
|
}
|
|
|
|
.track-card dl {
|
|
display: grid;
|
|
gap: 7px;
|
|
margin: 0;
|
|
font-size: 0.86rem;
|
|
}
|
|
|
|
.track-card dt {
|
|
color: var(--muted);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.track-card dd {
|
|
margin: 1px 0 0;
|
|
}
|
|
|
|
code {
|
|
padding: 1px 5px;
|
|
color: #27364f;
|
|
background: #edf2f7;
|
|
border: 1px solid #d9e1ec;
|
|
border-radius: 5px;
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
font-size: 0.86em;
|
|
}
|
|
|
|
.builder-grid {
|
|
display: grid;
|
|
grid-template-columns: minmax(260px, 0.9fr) minmax(0, 1.4fr);
|
|
gap: 14px;
|
|
align-items: start;
|
|
}
|
|
|
|
.panel {
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
padding: 14px;
|
|
}
|
|
|
|
.panel h3 {
|
|
margin: 0 0 10px;
|
|
font-size: 1rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.field {
|
|
display: grid;
|
|
gap: 6px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.field label {
|
|
color: var(--muted);
|
|
font-size: 0.78rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
select,
|
|
input[type="search"] {
|
|
width: 100%;
|
|
min-height: 40px;
|
|
padding: 8px 10px;
|
|
color: var(--ink);
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.belt-buttons {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.belt-buttons button,
|
|
.chip-button {
|
|
min-height: 40px;
|
|
padding: 8px 10px;
|
|
color: var(--ink);
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.belt-buttons button[aria-pressed="true"],
|
|
.chip-button[aria-pressed="true"] {
|
|
color: #ffffff;
|
|
background: var(--blue);
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
.belt-stage {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, minmax(110px, 1fr));
|
|
gap: 8px;
|
|
margin: 0 0 14px;
|
|
}
|
|
|
|
.stage-pill {
|
|
min-height: 76px;
|
|
padding: 10px;
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.stage-pill strong {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.belt-detail {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 10px;
|
|
}
|
|
|
|
.detail-box {
|
|
min-height: 104px;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
font-size: 0.87rem;
|
|
}
|
|
|
|
.detail-box strong {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
color: var(--ink);
|
|
}
|
|
|
|
.detail-box span {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.demo-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(260px, 0.95fr) minmax(0, 1.35fr);
|
|
gap: 14px;
|
|
}
|
|
|
|
.knob-stack {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.control-row {
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.control-row label {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 8px;
|
|
color: var(--ink);
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.control-row output {
|
|
color: var(--blue);
|
|
font-weight: 800;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
input[type="range"] {
|
|
width: 100%;
|
|
accent-color: var(--blue);
|
|
}
|
|
|
|
.segmented {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
overflow: hidden;
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.segmented button {
|
|
min-height: 40px;
|
|
padding: 7px;
|
|
color: var(--muted);
|
|
background: #ffffff;
|
|
border: 0;
|
|
border-right: 1px solid var(--line);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.segmented button:last-child {
|
|
border-right: 0;
|
|
}
|
|
|
|
.segmented button[aria-pressed="true"] {
|
|
color: #ffffff;
|
|
background: var(--teal);
|
|
}
|
|
|
|
.check-list {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.check-list label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin: 0;
|
|
color: var(--ink);
|
|
font-size: 0.88rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
input[type="checkbox"] {
|
|
width: 16px;
|
|
height: 16px;
|
|
accent-color: var(--blue);
|
|
}
|
|
|
|
.evidence-board {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.constraint-row {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.constraint {
|
|
display: grid;
|
|
grid-template-columns: 112px minmax(0, 1fr) 84px;
|
|
gap: 10px;
|
|
align-items: center;
|
|
font-size: 0.86rem;
|
|
}
|
|
|
|
.bar {
|
|
position: relative;
|
|
height: 14px;
|
|
overflow: hidden;
|
|
background: #e6ebf3;
|
|
border: 1px solid #d1d9e5;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.bar span {
|
|
display: block;
|
|
height: 100%;
|
|
width: 40%;
|
|
background: var(--green);
|
|
}
|
|
|
|
.constraint.warn .bar span {
|
|
background: var(--amber);
|
|
}
|
|
|
|
.constraint.fail .bar span {
|
|
background: var(--red);
|
|
}
|
|
|
|
.status {
|
|
font-weight: 800;
|
|
text-align: right;
|
|
}
|
|
|
|
.status.pass {
|
|
color: var(--green);
|
|
}
|
|
|
|
.status.warn {
|
|
color: var(--amber);
|
|
}
|
|
|
|
.status.fail {
|
|
color: var(--red);
|
|
}
|
|
|
|
.mini-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.mini-visual {
|
|
min-height: 176px;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.mini-visual h3 {
|
|
margin: 0 0 10px;
|
|
font-size: 0.94rem;
|
|
}
|
|
|
|
.pareto {
|
|
position: relative;
|
|
height: 122px;
|
|
background:
|
|
linear-gradient(to right, rgba(89, 101, 121, 0.16) 1px, transparent 1px),
|
|
linear-gradient(to bottom, rgba(89, 101, 121, 0.16) 1px, transparent 1px);
|
|
background-size: 34px 30px;
|
|
border-left: 2px solid #6b7280;
|
|
border-bottom: 2px solid #6b7280;
|
|
}
|
|
|
|
.point {
|
|
position: absolute;
|
|
width: 12px;
|
|
height: 12px;
|
|
margin: -6px 0 0 -6px;
|
|
background: var(--blue);
|
|
border: 2px solid #ffffff;
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 0 1px rgba(23, 32, 51, 0.2);
|
|
}
|
|
|
|
.point.bad {
|
|
background: var(--red);
|
|
}
|
|
|
|
.point.selected {
|
|
width: 16px;
|
|
height: 16px;
|
|
margin: -8px 0 0 -8px;
|
|
background: var(--green);
|
|
box-shadow: 0 0 0 3px rgba(8, 127, 91, 0.2);
|
|
}
|
|
|
|
.heatmap {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 3px;
|
|
}
|
|
|
|
.cell {
|
|
aspect-ratio: 1;
|
|
border-radius: 4px;
|
|
background: #dce5ef;
|
|
border: 1px solid #cfdae7;
|
|
}
|
|
|
|
.cell.ok {
|
|
background: #b8e0d0;
|
|
}
|
|
|
|
.cell.warn {
|
|
background: #ffd8a8;
|
|
}
|
|
|
|
.cell.fail {
|
|
background: #f3a8b6;
|
|
}
|
|
|
|
.stack {
|
|
display: grid;
|
|
gap: 7px;
|
|
}
|
|
|
|
.stack-bar {
|
|
display: grid;
|
|
grid-template-columns: 76px minmax(0, 1fr) 46px;
|
|
gap: 8px;
|
|
align-items: center;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.stack-track {
|
|
height: 14px;
|
|
overflow: hidden;
|
|
background: #e6ebf3;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.stack-track span {
|
|
display: block;
|
|
height: 100%;
|
|
background: var(--purple);
|
|
}
|
|
|
|
.timeline {
|
|
display: grid;
|
|
gap: 7px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.event {
|
|
display: grid;
|
|
grid-template-columns: 70px minmax(0, 1fr);
|
|
gap: 8px;
|
|
padding-bottom: 7px;
|
|
border-bottom: 1px solid var(--line);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.event:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.event strong {
|
|
color: var(--teal);
|
|
}
|
|
|
|
.topology {
|
|
min-height: 120px;
|
|
}
|
|
|
|
.topology svg {
|
|
width: 100%;
|
|
height: 120px;
|
|
display: block;
|
|
}
|
|
|
|
.toolbar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 2;
|
|
display: grid;
|
|
grid-template-columns: minmax(220px, 1fr) 170px 170px auto;
|
|
gap: 10px;
|
|
align-items: center;
|
|
padding: 12px;
|
|
margin-bottom: 14px;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.chip-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.chip-button {
|
|
min-height: 34px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.catalog-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.modality {
|
|
display: grid;
|
|
gap: 10px;
|
|
min-height: 280px;
|
|
padding: 14px;
|
|
background: #ffffff;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.modality[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.modality-head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
|
|
.modality h3 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.kind {
|
|
display: inline-block;
|
|
flex: 0 0 auto;
|
|
padding: 3px 7px;
|
|
color: #ffffff;
|
|
background: var(--blue);
|
|
border-radius: 999px;
|
|
font-size: 0.68rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.kind.opening {
|
|
background: var(--blue);
|
|
}
|
|
|
|
.kind.prediction {
|
|
background: var(--teal);
|
|
}
|
|
|
|
.kind.control {
|
|
background: var(--green);
|
|
}
|
|
|
|
.kind.visual {
|
|
background: var(--amber);
|
|
}
|
|
|
|
.kind.evidence {
|
|
background: var(--red);
|
|
}
|
|
|
|
.kind.decision {
|
|
background: var(--purple);
|
|
}
|
|
|
|
.modality p {
|
|
margin: 0;
|
|
color: var(--muted);
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.modality dl {
|
|
display: grid;
|
|
gap: 7px;
|
|
margin: 0;
|
|
font-size: 0.84rem;
|
|
}
|
|
|
|
.modality dt {
|
|
color: var(--muted);
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.modality dd {
|
|
margin: 1px 0 0;
|
|
}
|
|
|
|
.tag-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
align-content: start;
|
|
}
|
|
|
|
.tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 24px;
|
|
padding: 3px 7px;
|
|
color: #27364f;
|
|
background: #edf2f7;
|
|
border: 1px solid #d9e1ec;
|
|
border-radius: 999px;
|
|
font-size: 0.76rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.recipe-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
overflow: hidden;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
.recipe-table th,
|
|
.recipe-table td {
|
|
padding: 10px;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
|
|
.recipe-table th {
|
|
color: var(--ink);
|
|
background: #edf2f7;
|
|
font-size: 0.78rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.recipe-table tr:last-child td {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.empty-state {
|
|
display: none;
|
|
padding: 18px;
|
|
color: var(--muted);
|
|
background: #f8fafc;
|
|
border: 1px dashed var(--line);
|
|
border-radius: var(--radius);
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-state.visible {
|
|
display: block;
|
|
}
|
|
|
|
.source-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.source-list a,
|
|
.source-list div {
|
|
display: block;
|
|
min-height: 92px;
|
|
padding: 12px;
|
|
color: var(--ink);
|
|
background: #f8fafc;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.source-list strong {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
font-size: 0.94rem;
|
|
}
|
|
|
|
.source-list span {
|
|
display: block;
|
|
color: var(--muted);
|
|
font-size: 0.84rem;
|
|
}
|
|
|
|
@media (max-width: 1180px) {
|
|
.workflow,
|
|
.track-grid,
|
|
.hero-grid,
|
|
.catalog-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.toolbar {
|
|
grid-template-columns: minmax(220px, 1fr) repeat(2, 150px);
|
|
}
|
|
|
|
.toolbar .result-count {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.belt-stage,
|
|
.belt-detail,
|
|
.source-list {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 840px) {
|
|
.shell {
|
|
display: block;
|
|
}
|
|
|
|
.sidebar {
|
|
position: static;
|
|
height: auto;
|
|
}
|
|
|
|
main {
|
|
padding: 16px;
|
|
}
|
|
|
|
.builder-grid,
|
|
.demo-layout,
|
|
.mini-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.toolbar,
|
|
.workflow,
|
|
.track-grid,
|
|
.hero-grid,
|
|
.catalog-grid,
|
|
.belt-stage,
|
|
.belt-detail,
|
|
.source-list {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.constraint {
|
|
grid-template-columns: 84px minmax(0, 1fr) 74px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="shell">
|
|
<aside class="sidebar">
|
|
<div class="brand">
|
|
<img src="assets/images/logo.png" alt="MLSysBook logo">
|
|
<div>
|
|
<strong>Lab Belts And Knobs</strong>
|
|
<span>Track-aware content catalog</span>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-group" aria-label="Page sections">
|
|
<p class="nav-title">Catalog</p>
|
|
<div class="nav-links">
|
|
<a href="#system">System</a>
|
|
<a href="#tracks">Tracks</a>
|
|
<a href="#belts">Belts</a>
|
|
<a href="#knobs">Knob Demo</a>
|
|
<a href="#modalities">Modalities</a>
|
|
<a href="#recipes">Recipes</a>
|
|
<a href="#sources">Sources</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="sidebar-note">
|
|
Modalities render content. They do not own hardware facts, solver equations, or scenario constants.
|
|
</div>
|
|
</aside>
|
|
|
|
<main>
|
|
<header class="hero">
|
|
<p class="eyebrow">MLSysBook Labs</p>
|
|
<h1>Reusable belts and knobs for track-aware lab content</h1>
|
|
<p class="lead">
|
|
A local reference for the components used to assemble labs: fixed pedagogical flow, track-specific realization, interactive controls, evidence views, source traces, reflections, decisions, and downloadable reports.
|
|
</p>
|
|
|
|
<div class="hero-grid" aria-label="Catalog summary">
|
|
<div class="metric">
|
|
<strong>4</strong>
|
|
<span>canonical tracks: iPhone, Oura Ring, RoboTaxi, Cloud Fleet</span>
|
|
</div>
|
|
<div class="metric">
|
|
<strong>5</strong>
|
|
<span>repeatable belts for opening, prediction, trade-off, synthesis, and report</span>
|
|
</div>
|
|
<div class="metric">
|
|
<strong>18</strong>
|
|
<span>modalities across controls, plots, evidence, and decisions</span>
|
|
</div>
|
|
<div class="metric">
|
|
<strong>1</strong>
|
|
<span>source-of-truth rule: facts and equations come from MLSysIM</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section id="system">
|
|
<h2>System</h2>
|
|
<p class="section-note">
|
|
Every lab combines the same three layers. The layer boundary is what keeps track changes clean instead of scattering constants through notebooks.
|
|
</p>
|
|
<div class="workflow">
|
|
<div class="flow-step">
|
|
<span class="step-index">Layer 1</span>
|
|
<h3>Pedagogy Kernel</h3>
|
|
<p>Concept, prediction, evidence, reflection, decision, and takeaway remain stable across tracks.</p>
|
|
</div>
|
|
<div class="flow-step">
|
|
<span class="step-index">Layer 2</span>
|
|
<h3>Track Realization</h3>
|
|
<p>Track profile changes narrative, defaults, hardware refs, metrics, thresholds, and stakeholder pressure.</p>
|
|
</div>
|
|
<div class="flow-step">
|
|
<span class="step-index">Layer 3</span>
|
|
<h3>Modality Stack</h3>
|
|
<p>Selectors, sliders, plots, tables, callouts, ledgers, and report components make the idea visible.</p>
|
|
</div>
|
|
<div class="flow-step">
|
|
<span class="step-index">Boundary</span>
|
|
<h3>Single Source</h3>
|
|
<p>MLSysIM owns hardware, model, infrastructure facts, and solver equations. Labs own only typed scenario variants.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tracks">
|
|
<h2>Canonical Tracks</h2>
|
|
<p class="section-note">
|
|
Students should pick one track once. Later labs read that track from the Design Ledger unless an instructor allows switching.
|
|
</p>
|
|
<div class="track-grid">
|
|
<article class="track-card">
|
|
<h3>iPhone <span class="category">Mobile</span></h3>
|
|
<dl>
|
|
<div>
|
|
<dt>Hardware source</dt>
|
|
<dd><code>Hardware.Mobile.iPhone15Pro</code></dd>
|
|
</div>
|
|
<div>
|
|
<dt>Primary metrics</dt>
|
|
<dd>Battery drain, thermal headroom, on-device latency, memory</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Typical narrative</dt>
|
|
<dd>Ship a responsive local model without overheating or draining the phone.</dd>
|
|
</div>
|
|
</dl>
|
|
</article>
|
|
|
|
<article class="track-card">
|
|
<h3>Oura Ring <span class="category">TinyML</span></h3>
|
|
<dl>
|
|
<div>
|
|
<dt>Hardware source</dt>
|
|
<dd><code>Hardware.Tiny.OuraRing</code></dd>
|
|
</div>
|
|
<div>
|
|
<dt>Primary metrics</dt>
|
|
<dd>SRAM and flash fit, battery life, OTA payload, sampling cadence</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Typical narrative</dt>
|
|
<dd>Fit sensing and inference into a tiny wearable budget.</dd>
|
|
</div>
|
|
</dl>
|
|
</article>
|
|
|
|
<article class="track-card">
|
|
<h3>RoboTaxi <span class="category">Edge</span></h3>
|
|
<dl>
|
|
<div>
|
|
<dt>Hardware source</dt>
|
|
<dd><code>Hardware.Edge.RoboTaxi</code></dd>
|
|
</div>
|
|
<div>
|
|
<dt>Primary metrics</dt>
|
|
<dd>p99 and p999 latency, rare-event recall, power, reliability</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Typical narrative</dt>
|
|
<dd>Keep perception and planning inside tight latency and safety guardrails.</dd>
|
|
</div>
|
|
</dl>
|
|
</article>
|
|
|
|
<article class="track-card">
|
|
<h3>Cloud Fleet <span class="category">Fleet</span></h3>
|
|
<dl>
|
|
<div>
|
|
<dt>Hardware source</dt>
|
|
<dd>Cloud fleet profile backed initially by <code>Hardware.Cloud.H100</code></dd>
|
|
</div>
|
|
<div>
|
|
<dt>Primary metrics</dt>
|
|
<dd>Throughput, p99 latency, cost/request, utilization, carbon</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Typical narrative</dt>
|
|
<dd>Run a service under SLA, cost, capacity, and sustainability constraints.</dd>
|
|
</div>
|
|
</dl>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="belts">
|
|
<h2>Belts</h2>
|
|
<p class="section-note">
|
|
Belts are repeatable lab flows. Pick the flow, then plug in the track variant and modality stack.
|
|
</p>
|
|
<div class="builder-grid">
|
|
<div class="panel">
|
|
<h3>Flow selector</h3>
|
|
<div class="belt-buttons" role="group" aria-label="Select a lab belt">
|
|
<button type="button" data-belt="opening" aria-pressed="true">Opening Belt</button>
|
|
<button type="button" data-belt="nugget" aria-pressed="false">Part / Nugget Belt</button>
|
|
<button type="button" data-belt="tradeoff" aria-pressed="false">Trade-Off Belt</button>
|
|
<button type="button" data-belt="synthesis" aria-pressed="false">Synthesis Belt</button>
|
|
<button type="button" data-belt="report" aria-pressed="false">Report Belt</button>
|
|
</div>
|
|
</div>
|
|
<div class="panel">
|
|
<h3 id="belt-title">Opening Belt</h3>
|
|
<div class="belt-stage" id="belt-stages"></div>
|
|
<div class="belt-detail">
|
|
<div class="detail-box">
|
|
<strong>Pedagogical job</strong>
|
|
<span id="belt-job"></span>
|
|
</div>
|
|
<div class="detail-box">
|
|
<strong>Core modalities</strong>
|
|
<span id="belt-modalities"></span>
|
|
</div>
|
|
<div class="detail-box">
|
|
<strong>Output</strong>
|
|
<span id="belt-output"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="knobs">
|
|
<h2>Knob Demo</h2>
|
|
<p class="section-note">
|
|
The same component can render different narratives and constraints after the track changes. Values below are illustrative placeholders; implementation should pull real facts from MLSysIM.
|
|
</p>
|
|
<div class="demo-layout">
|
|
<div class="panel">
|
|
<h3>Inputs</h3>
|
|
<div class="field">
|
|
<label for="trackSelect">Track</label>
|
|
<select id="trackSelect">
|
|
<option value="iphone">iPhone</option>
|
|
<option value="oura">Oura Ring</option>
|
|
<option value="robotaxi">RoboTaxi</option>
|
|
<option value="cloud">Cloud Fleet</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="knob-stack">
|
|
<div class="control-row">
|
|
<label for="bitWidth">Primary knob <output id="bitOut">6 bit</output></label>
|
|
<input id="bitWidth" type="range" min="2" max="16" value="6" step="1">
|
|
</div>
|
|
<div class="control-row">
|
|
<label>Strategy</label>
|
|
<div class="segmented" role="group" aria-label="Strategy selector">
|
|
<button type="button" data-strategy="quantize" aria-pressed="true">Quantize</button>
|
|
<button type="button" data-strategy="prune" aria-pressed="false">Prune</button>
|
|
<button type="button" data-strategy="distill" aria-pressed="false">Distill</button>
|
|
</div>
|
|
</div>
|
|
<div class="control-row">
|
|
<label>Stack builder</label>
|
|
<div class="check-list">
|
|
<label><input type="checkbox" data-stack="calibration" checked> Calibration pass</label>
|
|
<label><input type="checkbox" data-stack="guardrail" checked> Guardrail validation</label>
|
|
<label><input type="checkbox" data-stack="rollback"> Rollback plan</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel evidence-board">
|
|
<h3 id="demo-title">iPhone realization</h3>
|
|
<div class="constraint-row" id="constraints"></div>
|
|
|
|
<div class="mini-grid">
|
|
<div class="mini-visual">
|
|
<h3>Pareto Frontier</h3>
|
|
<div class="pareto" aria-label="Illustrative Pareto frontier">
|
|
<span class="point" style="left: 16%; top: 76%;"></span>
|
|
<span class="point" style="left: 33%; top: 58%;"></span>
|
|
<span class="point selected" id="selectedPoint" style="left: 52%; top: 42%;"></span>
|
|
<span class="point" style="left: 70%; top: 31%;"></span>
|
|
<span class="point bad" style="left: 86%; top: 24%;"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mini-visual">
|
|
<h3>Phase Diagram</h3>
|
|
<div class="heatmap" id="heatmap" aria-label="Illustrative feasibility heatmap"></div>
|
|
</div>
|
|
|
|
<div class="mini-visual">
|
|
<h3>Budget Stack</h3>
|
|
<div class="stack" id="budgetStack"></div>
|
|
</div>
|
|
|
|
<div class="mini-visual">
|
|
<h3>Timeline / Topology</h3>
|
|
<div class="timeline" id="timeline"></div>
|
|
<div class="topology" aria-label="Illustrative topology diagram">
|
|
<svg viewBox="0 0 360 120" role="img" aria-labelledby="topologyTitle topologyDesc">
|
|
<title id="topologyTitle">Sensor to compute topology</title>
|
|
<desc id="topologyDesc">A simple path from source to device, edge, and report.</desc>
|
|
<defs>
|
|
<marker id="arrow" viewBox="0 0 10 10" refX="7" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#596579"></path>
|
|
</marker>
|
|
</defs>
|
|
<line x1="66" y1="62" x2="138" y2="62" stroke="#596579" stroke-width="2" marker-end="url(#arrow)"></line>
|
|
<line x1="206" y1="62" x2="278" y2="62" stroke="#596579" stroke-width="2" marker-end="url(#arrow)"></line>
|
|
<rect x="18" y="34" width="64" height="56" rx="7" fill="#e7f5ff" stroke="#8ecae6"></rect>
|
|
<rect x="146" y="34" width="64" height="56" rx="7" fill="#e6fcf5" stroke="#63c7a3"></rect>
|
|
<rect x="274" y="34" width="64" height="56" rx="7" fill="#fff4e6" stroke="#f0ad4e"></rect>
|
|
<text x="50" y="66" text-anchor="middle" font-size="12" fill="#172033">Input</text>
|
|
<text x="178" y="66" text-anchor="middle" font-size="12" fill="#172033">Solver</text>
|
|
<text x="306" y="66" text-anchor="middle" font-size="12" fill="#172033">Report</text>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="modalities">
|
|
<h2>Modality Catalog</h2>
|
|
<p class="section-note">
|
|
Search or filter by role. Each entry names when to use it, what the student does, and the shape of the output contract.
|
|
</p>
|
|
|
|
<div class="toolbar">
|
|
<input id="search" type="search" placeholder="Search modalities, contracts, metrics, or tracks" aria-label="Search modalities">
|
|
<select id="kindFilter" aria-label="Filter by modality kind">
|
|
<option value="all">All kinds</option>
|
|
<option value="opening">Opening</option>
|
|
<option value="prediction">Prediction</option>
|
|
<option value="control">Control</option>
|
|
<option value="visual">Visual</option>
|
|
<option value="evidence">Evidence</option>
|
|
<option value="decision">Decision</option>
|
|
</select>
|
|
<select id="trackFilter" aria-label="Filter by track">
|
|
<option value="all">All tracks</option>
|
|
<option value="iphone">iPhone</option>
|
|
<option value="oura">Oura Ring</option>
|
|
<option value="robotaxi">RoboTaxi</option>
|
|
<option value="cloud">Cloud Fleet</option>
|
|
</select>
|
|
<strong class="result-count" id="resultCount">18 items</strong>
|
|
</div>
|
|
|
|
<div class="chip-row" role="group" aria-label="Teaching goal filters">
|
|
<button class="chip-button" type="button" data-goal="all" aria-pressed="true">All goals</button>
|
|
<button class="chip-button" type="button" data-goal="threshold" aria-pressed="false">Threshold</button>
|
|
<button class="chip-button" type="button" data-goal="tradeoff" aria-pressed="false">Trade-off</button>
|
|
<button class="chip-button" type="button" data-goal="scale" aria-pressed="false">Scale</button>
|
|
<button class="chip-button" type="button" data-goal="structure" aria-pressed="false">Structure</button>
|
|
<button class="chip-button" type="button" data-goal="judgment" aria-pressed="false">Judgment</button>
|
|
</div>
|
|
|
|
<div class="catalog-grid" id="catalogGrid"></div>
|
|
<div class="empty-state" id="emptyState">No modalities match the current filters.</div>
|
|
</section>
|
|
|
|
<section id="recipes">
|
|
<h2>Recipes</h2>
|
|
<p class="section-note">
|
|
These recipes show how belts and knobs combine into a lab part or an entire compression pilot.
|
|
</p>
|
|
|
|
<table class="recipe-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Lab unit</th>
|
|
<th>Pedagogical move</th>
|
|
<th>Recommended stack</th>
|
|
<th>Report evidence</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Opening</td>
|
|
<td>Anchor the chapter and selected track.</td>
|
|
<td>Track selector, chapter recap, scenario strip, lab map.</td>
|
|
<td>Track ID, hardware ref, scenario ID, learning objectives.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Part A</td>
|
|
<td>Make a misconception visible before reveal.</td>
|
|
<td>Multiple-choice prediction, strategy selector, constraint budget, source trace.</td>
|
|
<td>Prediction, actual outcome, failed or binding constraint.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Part B</td>
|
|
<td>Show that the answer is a frontier, not a single knob.</td>
|
|
<td>Numeric prediction, slider, Pareto frontier, phase diagram, table fallback.</td>
|
|
<td>Selected point, dominated choices, guardrail violations, reflection.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Part C</td>
|
|
<td>Build a deployable recipe and name residual risk.</td>
|
|
<td>Stack builder, budget stack, before/after comparison, decision card.</td>
|
|
<td>Final configuration, primary metric, guardrail metric, residual risk.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Synthesis</td>
|
|
<td>Defend an engineering judgment.</td>
|
|
<td>Evidence summary, big takeaways, source trace, report export.</td>
|
|
<td>Markdown report plus optional JSON snapshot.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section id="sources">
|
|
<h2>Source Documents</h2>
|
|
<p class="section-note">
|
|
This page is a visual companion to the planning docs. The docs remain the planning source.
|
|
</p>
|
|
<div class="source-list">
|
|
<a href="LAB_REALIZATION_MODALITY_CATALOG.md">
|
|
<strong>Modality Catalog</strong>
|
|
<span>Reusable components, controls, plots, evidence views, and report artifacts.</span>
|
|
</a>
|
|
<a href="LAB_STRUCTURE_AND_REPORT_CONTRACT.md">
|
|
<strong>Structure Contract</strong>
|
|
<span>Required lab headers, part headers, synthesis sections, and report schema.</span>
|
|
</a>
|
|
<a href="LAB_SINGLE_SOURCE_OF_TRUTH_POLICY.md">
|
|
<strong>Source-Of-Truth Policy</strong>
|
|
<span>Ownership boundary for MLSysIM facts, solver equations, and lab variants.</span>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
const belts = {
|
|
opening: {
|
|
title: "Opening Belt",
|
|
stages: ["Learning Objectives", "Chapter Recap", "Your Track", "Scenario Brief", "Lab Map"],
|
|
job: "Turn a chapter into an assignable systems scenario before students touch controls.",
|
|
modalities: "Track selector, chapter recap, scenario strip, lab map.",
|
|
output: "Track ID, hardware ref, scenario ID, objectives, part completion plan."
|
|
},
|
|
nugget: {
|
|
title: "Part / Nugget Belt",
|
|
stages: ["What You Need To Know", "Scenario Slice", "Your Prediction", "Try It", "Checkpoint"],
|
|
job: "Teach one focused concept through prediction, manipulation, evidence, and saved reasoning.",
|
|
modalities: "Prediction lock, primary knob slider, evidence panel, source trace, reflection card.",
|
|
output: "Prediction, knob settings, result snapshot, binding constraint, reflection."
|
|
},
|
|
tradeoff: {
|
|
title: "Trade-Off Belt",
|
|
stages: ["Choose Strategy", "Sweep Knob", "Inspect Frontier", "Check Guardrail", "Defend Point"],
|
|
job: "Show that deployable ML systems rarely have a single best answer.",
|
|
modalities: "Strategy selector, sliders, Pareto frontier, phase diagram, constraint budget.",
|
|
output: "Selected candidate, dominated alternatives, guardrail violations, residual risk."
|
|
},
|
|
synthesis: {
|
|
title: "Synthesis Belt",
|
|
stages: ["Evidence Summary", "Final Decision", "Big Takeaways", "Residual Risk", "Next Consequence"],
|
|
job: "Convert part-level observations into one defensible engineering judgment.",
|
|
modalities: "Ledger summary, decision card, big takeaways, source trace.",
|
|
output: "Final decision, evidence summary, takeaways, invalidating assumption."
|
|
},
|
|
report: {
|
|
title: "Report Belt",
|
|
stages: ["Lab", "Track", "Predictions", "Evidence", "Source Trace"],
|
|
job: "Produce a local submission artifact even when fields are incomplete.",
|
|
modalities: "Design Ledger, report export, incomplete-fields notice, fallback text area.",
|
|
output: "Markdown report and optional JSON snapshot generated locally."
|
|
}
|
|
};
|
|
|
|
const tracks = {
|
|
iphone: {
|
|
label: "iPhone realization",
|
|
metrics: [
|
|
["Battery", 54, "pass"],
|
|
["Thermal", 68, "warn"],
|
|
["Latency", 41, "pass"],
|
|
["Memory", 57, "pass"]
|
|
],
|
|
budget: [["Weights", 42], ["Activations", 28], ["Runtime", 18], ["Headroom", 12]],
|
|
timeline: [["T0", "Track selected from local ledger"], ["T1", "On-device latency predicted"], ["T2", "Thermal guardrail checked"]]
|
|
},
|
|
oura: {
|
|
label: "Oura Ring realization",
|
|
metrics: [
|
|
["SRAM", 81, "warn"],
|
|
["Flash", 73, "warn"],
|
|
["Battery", 48, "pass"],
|
|
["OTA", 66, "warn"]
|
|
],
|
|
budget: [["Weights", 36], ["Buffers", 31], ["Radio", 20], ["Headroom", 13]],
|
|
timeline: [["T0", "Sampling cadence selected"], ["T1", "Memory fit checked"], ["T2", "OTA payload reviewed"]]
|
|
},
|
|
robotaxi: {
|
|
label: "RoboTaxi realization",
|
|
metrics: [
|
|
["p99", 76, "warn"],
|
|
["p999", 88, "fail"],
|
|
["Power", 63, "warn"],
|
|
["Recall", 46, "pass"]
|
|
],
|
|
budget: [["Perception", 43], ["Planning", 24], ["Transfer", 19], ["Headroom", 14]],
|
|
timeline: [["T0", "Sensor workload selected"], ["T1", "Tail latency inspected"], ["T2", "Rare-event recall protected"]]
|
|
},
|
|
cloud: {
|
|
label: "Cloud Fleet realization",
|
|
metrics: [
|
|
["SLA", 61, "pass"],
|
|
["Cost", 69, "warn"],
|
|
["Utilization", 74, "warn"],
|
|
["Carbon", 52, "pass"]
|
|
],
|
|
budget: [["Compute", 48], ["Network", 17], ["Storage", 13], ["Headroom", 22]],
|
|
timeline: [["T0", "Fleet scenario loaded"], ["T1", "Replica count swept"], ["T2", "SLA and cost reported"]]
|
|
}
|
|
};
|
|
|
|
const modalities = [
|
|
{
|
|
title: "Track Selector",
|
|
kind: "opening",
|
|
use: "Start Lab 00 and every track-aware lab from one system identity.",
|
|
action: "Choose iPhone, Oura Ring, RoboTaxi, or Cloud Fleet.",
|
|
contract: "track_id, category, hardware_ref, scenario_id, primary_constraints",
|
|
goals: ["structure", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["ledger", "track", "hardware"]
|
|
},
|
|
{
|
|
title: "Chapter Recap",
|
|
kind: "opening",
|
|
use: "Bridge the book chapter into the systems scenario.",
|
|
action: "Read the key idea, systems translation, and common trap.",
|
|
contract: "ChapterRecap",
|
|
goals: ["structure"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["recap", "concept", "trap"]
|
|
},
|
|
{
|
|
title: "Scenario Strip",
|
|
kind: "opening",
|
|
use: "Keep stakeholder, workload, model, metrics, and guardrails visible.",
|
|
action: "Scan the active scenario before interacting.",
|
|
contract: "track_id, stakeholder, hardware_ref, model_ref, workload_summary, objective",
|
|
goals: ["structure", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["scenario", "stakeholder", "metrics"]
|
|
},
|
|
{
|
|
title: "Multiple-Choice Prediction Lock",
|
|
kind: "prediction",
|
|
use: "Reveal a misconception before showing computed evidence.",
|
|
action: "Pick one outcome and lock it before reveal.",
|
|
contract: "prediction_id, selected_option, correct_option, reason",
|
|
goals: ["threshold", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["prediction", "misconception", "lock"]
|
|
},
|
|
{
|
|
title: "Numeric Prediction Lock",
|
|
kind: "prediction",
|
|
use: "Make students estimate a magnitude before computing it.",
|
|
action: "Enter a unit-bearing value and compare against actual.",
|
|
contract: "prediction_id, predicted_value, unit, actual_value, error_factor",
|
|
goals: ["threshold", "scale"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["magnitude", "estimate", "unit"]
|
|
},
|
|
{
|
|
title: "Primary Knob Slider",
|
|
kind: "control",
|
|
use: "Expose a threshold, cliff, or nonlinear transition.",
|
|
action: "Move one continuous variable and watch linked outputs.",
|
|
contract: "knob_id, mlsysim_param, value, unit, watched_outputs",
|
|
goals: ["threshold", "scale", "tradeoff"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["slider", "bit width", "batch", "cadence"]
|
|
},
|
|
{
|
|
title: "Strategy Selector",
|
|
kind: "control",
|
|
use: "Choose one engineering option from a small set.",
|
|
action: "Pick a strategy with a short trade-off label.",
|
|
contract: "strategy_id, selected_strategy, available_strategies, unsupported_warnings",
|
|
goals: ["tradeoff", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["strategy", "radio", "choice"]
|
|
},
|
|
{
|
|
title: "Stack Builder",
|
|
kind: "control",
|
|
use: "Compose interventions with cumulative cost and benefit.",
|
|
action: "Select compatible options and inspect interactions.",
|
|
contract: "stack_id, selected_items, cumulative_costs, cumulative_benefits, interaction_warnings",
|
|
goals: ["tradeoff", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["stack", "checkbox", "recipe"]
|
|
},
|
|
{
|
|
title: "Constraint Budget",
|
|
kind: "visual",
|
|
use: "Show which resource is closest to violation.",
|
|
action: "Compare value, limit, headroom, and binding constraint.",
|
|
contract: "ConstraintBudget, BottleneckReport",
|
|
goals: ["threshold", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["budget", "bottleneck", "guardrail"]
|
|
},
|
|
{
|
|
title: "Pareto Frontier",
|
|
kind: "visual",
|
|
use: "Show multi-objective trade-offs with no single best choice.",
|
|
action: "Pick a point and explain dominated choices or guardrail violations.",
|
|
contract: "ParetoFrontier, selected_candidate, dominated, violations",
|
|
goals: ["tradeoff", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["frontier", "scatter", "trade-off"]
|
|
},
|
|
{
|
|
title: "Phase Diagram / Heatmap",
|
|
kind: "visual",
|
|
use: "Show how two knobs jointly determine feasibility.",
|
|
action: "Move an operating point across feasible and infeasible regimes.",
|
|
contract: "SensitivityGrid, regime_labels, current_point",
|
|
goals: ["threshold", "scale", "tradeoff"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["heatmap", "grid", "regime"]
|
|
},
|
|
{
|
|
title: "Budget Stack",
|
|
kind: "visual",
|
|
use: "Decompose a total into meaningful parts.",
|
|
action: "Compare the contribution of weights, activations, network, radio, or operations.",
|
|
contract: "BudgetStack, components, total",
|
|
goals: ["tradeoff", "scale"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["stacked bar", "memory", "energy", "cost"]
|
|
},
|
|
{
|
|
title: "Before / After Comparison",
|
|
kind: "visual",
|
|
use: "Show the effect of one intervention.",
|
|
action: "Compare baseline, candidate, delta, and new bottleneck.",
|
|
contract: "baseline, candidate, deltas, new_bottleneck",
|
|
goals: ["tradeoff", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["delta", "baseline", "candidate"]
|
|
},
|
|
{
|
|
title: "Timeline",
|
|
kind: "visual",
|
|
use: "Show behavior that unfolds over time.",
|
|
action: "Inspect events, decision points, observed state, and true state.",
|
|
contract: "TimelineEvent, observed_metric, true_metric, decision_point",
|
|
goals: ["scale", "structure"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["drift", "rollout", "battery", "checkpoint"]
|
|
},
|
|
{
|
|
title: "Topology / Pipeline Diagram",
|
|
kind: "visual",
|
|
use: "Show structure when placement or dataflow matters.",
|
|
action: "Trace nodes, stages, links, capacities, and bottlenecks.",
|
|
contract: "nodes, edges, stage_capacities, active_bottleneck",
|
|
goals: ["structure", "scale"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["pipeline", "network", "placement"]
|
|
},
|
|
{
|
|
title: "Source Trace / Math Peek",
|
|
kind: "evidence",
|
|
use: "Connect numbers to MLSysIM facts, equations, and assumptions.",
|
|
action: "Open the trace to audit source, equation, and track-specific assumption.",
|
|
contract: "equation_id, mlsysim_api, hardware_ref, scenario_assumptions",
|
|
goals: ["structure", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["source", "equation", "provenance"]
|
|
},
|
|
{
|
|
title: "Failure Boundary Callout",
|
|
kind: "evidence",
|
|
use: "Name the first hard limit crossed by a selected configuration.",
|
|
action: "Read the failed value, limit, unit, and mitigation hint.",
|
|
contract: "failed_constraint, value, limit, mitigation_hint",
|
|
goals: ["threshold", "judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["failure", "limit", "mitigation"]
|
|
},
|
|
{
|
|
title: "Reflection / Decision / Report",
|
|
kind: "decision",
|
|
use: "Turn interaction into a saved engineering judgment and local artifact.",
|
|
action: "Save diagnosis, trade-off, residual risk, final decision, and report.",
|
|
contract: "decision_id, selected_configuration, primary_metric, guardrail_metric, Markdown report",
|
|
goals: ["judgment"],
|
|
tracks: ["iphone", "oura", "robotaxi", "cloud"],
|
|
tags: ["reflection", "ledger", "report"]
|
|
}
|
|
];
|
|
|
|
const beltButtons = Array.from(document.querySelectorAll("[data-belt]"));
|
|
const beltTitle = document.getElementById("belt-title");
|
|
const beltStages = document.getElementById("belt-stages");
|
|
const beltJob = document.getElementById("belt-job");
|
|
const beltModalities = document.getElementById("belt-modalities");
|
|
const beltOutput = document.getElementById("belt-output");
|
|
|
|
function renderBelt(key) {
|
|
const belt = belts[key];
|
|
beltTitle.textContent = belt.title;
|
|
beltStages.innerHTML = belt.stages.map((stage, index) => `
|
|
<div class="stage-pill">
|
|
<strong>${index + 1}. ${stage}</strong>
|
|
<span>${index === 0 ? "Start" : index === belt.stages.length - 1 ? "Commit" : "Build"}</span>
|
|
</div>
|
|
`).join("");
|
|
beltJob.textContent = belt.job;
|
|
beltModalities.textContent = belt.modalities;
|
|
beltOutput.textContent = belt.output;
|
|
beltButtons.forEach((button) => {
|
|
button.setAttribute("aria-pressed", String(button.dataset.belt === key));
|
|
});
|
|
}
|
|
|
|
beltButtons.forEach((button) => {
|
|
button.addEventListener("click", () => renderBelt(button.dataset.belt));
|
|
});
|
|
|
|
const trackSelect = document.getElementById("trackSelect");
|
|
const bitWidth = document.getElementById("bitWidth");
|
|
const bitOut = document.getElementById("bitOut");
|
|
const demoTitle = document.getElementById("demo-title");
|
|
const constraints = document.getElementById("constraints");
|
|
const budgetStack = document.getElementById("budgetStack");
|
|
const timeline = document.getElementById("timeline");
|
|
const heatmap = document.getElementById("heatmap");
|
|
const selectedPoint = document.getElementById("selectedPoint");
|
|
const strategyButtons = Array.from(document.querySelectorAll("[data-strategy]"));
|
|
|
|
function statusLabel(status) {
|
|
if (status === "fail") return "Fail";
|
|
if (status === "warn") return "Watch";
|
|
return "Pass";
|
|
}
|
|
|
|
function renderTrackDemo() {
|
|
const track = tracks[trackSelect.value];
|
|
const width = Number(bitWidth.value);
|
|
bitOut.textContent = `${width} bit`;
|
|
demoTitle.textContent = track.label;
|
|
|
|
const compressionEffect = Math.max(0, Math.min(18, 16 - width));
|
|
constraints.innerHTML = track.metrics.map(([label, base, status]) => {
|
|
const adjusted = Math.max(8, Math.min(98, base - compressionEffect));
|
|
const derivedStatus = adjusted > 84 ? "fail" : adjusted > 66 ? "warn" : status === "fail" ? "warn" : adjusted < 58 ? "pass" : status;
|
|
return `
|
|
<div class="constraint ${derivedStatus}">
|
|
<strong>${label}</strong>
|
|
<div class="bar"><span style="width: ${adjusted}%;"></span></div>
|
|
<span class="status ${derivedStatus}">${statusLabel(derivedStatus)}</span>
|
|
</div>
|
|
`;
|
|
}).join("");
|
|
|
|
budgetStack.innerHTML = track.budget.map(([label, value]) => {
|
|
const adjusted = label === "Headroom" ? value + Math.floor(compressionEffect / 2) : Math.max(8, value - Math.floor(compressionEffect / 4));
|
|
return `
|
|
<div class="stack-bar">
|
|
<strong>${label}</strong>
|
|
<div class="stack-track"><span style="width: ${adjusted}%;"></span></div>
|
|
<span>${adjusted}%</span>
|
|
</div>
|
|
`;
|
|
}).join("");
|
|
|
|
timeline.innerHTML = track.timeline.map(([time, event]) => `
|
|
<div class="event">
|
|
<strong>${time}</strong>
|
|
<span>${event}</span>
|
|
</div>
|
|
`).join("");
|
|
|
|
selectedPoint.style.left = `${Math.max(18, Math.min(82, 20 + width * 4))}%`;
|
|
selectedPoint.style.top = `${Math.max(22, Math.min(78, 78 - width * 3))}%`;
|
|
renderHeatmap(width);
|
|
}
|
|
|
|
function renderHeatmap(width) {
|
|
const cells = [];
|
|
for (let row = 0; row < 5; row += 1) {
|
|
for (let col = 0; col < 7; col += 1) {
|
|
const score = row * 1.2 + col * 0.8 + width / 6;
|
|
const klass = score > 8.2 ? "fail" : score > 6.2 ? "warn" : "ok";
|
|
cells.push(`<span class="cell ${klass}" aria-hidden="true"></span>`);
|
|
}
|
|
}
|
|
heatmap.innerHTML = cells.join("");
|
|
}
|
|
|
|
trackSelect.addEventListener("change", renderTrackDemo);
|
|
bitWidth.addEventListener("input", renderTrackDemo);
|
|
strategyButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
strategyButtons.forEach((candidate) => {
|
|
candidate.setAttribute("aria-pressed", String(candidate === button));
|
|
});
|
|
});
|
|
});
|
|
|
|
const catalogGrid = document.getElementById("catalogGrid");
|
|
const search = document.getElementById("search");
|
|
const kindFilter = document.getElementById("kindFilter");
|
|
const trackFilter = document.getElementById("trackFilter");
|
|
const resultCount = document.getElementById("resultCount");
|
|
const emptyState = document.getElementById("emptyState");
|
|
const goalButtons = Array.from(document.querySelectorAll("[data-goal]"));
|
|
let activeGoal = "all";
|
|
|
|
function modalityText(item) {
|
|
return [
|
|
item.title,
|
|
item.kind,
|
|
item.use,
|
|
item.action,
|
|
item.contract,
|
|
item.goals.join(" "),
|
|
item.tracks.join(" "),
|
|
item.tags.join(" ")
|
|
].join(" ").toLowerCase();
|
|
}
|
|
|
|
function renderCatalog() {
|
|
const query = search.value.trim().toLowerCase();
|
|
const kind = kindFilter.value;
|
|
const track = trackFilter.value;
|
|
let shown = 0;
|
|
|
|
catalogGrid.innerHTML = modalities.map((item) => {
|
|
const matchesQuery = !query || modalityText(item).includes(query);
|
|
const matchesKind = kind === "all" || item.kind === kind;
|
|
const matchesTrack = track === "all" || item.tracks.includes(track);
|
|
const matchesGoal = activeGoal === "all" || item.goals.includes(activeGoal);
|
|
const visible = matchesQuery && matchesKind && matchesTrack && matchesGoal;
|
|
if (visible) shown += 1;
|
|
return `
|
|
<article class="modality" ${visible ? "" : "hidden"}>
|
|
<div class="modality-head">
|
|
<h3>${item.title}</h3>
|
|
<span class="kind ${item.kind}">${item.kind}</span>
|
|
</div>
|
|
<p>${item.use}</p>
|
|
<dl>
|
|
<div>
|
|
<dt>Student action</dt>
|
|
<dd>${item.action}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Output contract</dt>
|
|
<dd>${item.contract}</dd>
|
|
</div>
|
|
</dl>
|
|
<div class="tag-list">
|
|
${item.tags.map((tag) => `<span class="tag">${tag}</span>`).join("")}
|
|
</div>
|
|
</article>
|
|
`;
|
|
}).join("");
|
|
|
|
resultCount.textContent = `${shown} ${shown === 1 ? "item" : "items"}`;
|
|
emptyState.classList.toggle("visible", shown === 0);
|
|
}
|
|
|
|
[search, kindFilter, trackFilter].forEach((control) => {
|
|
control.addEventListener("input", renderCatalog);
|
|
control.addEventListener("change", renderCatalog);
|
|
});
|
|
|
|
goalButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
activeGoal = button.dataset.goal;
|
|
goalButtons.forEach((candidate) => {
|
|
candidate.setAttribute("aria-pressed", String(candidate === button));
|
|
});
|
|
renderCatalog();
|
|
});
|
|
});
|
|
|
|
renderBelt("opening");
|
|
renderTrackDemo();
|
|
renderCatalog();
|
|
</script>
|
|
</body>
|
|
</html>
|