417 lines
15 KiB
HTML
417 lines
15 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Service Geometry Engine | Bryan Mubarak</title>
|
||
|
|
<meta property="og:type" content="website">
|
||
|
|
<meta property="og:title" content="Service Geometry Engine | Bryan Mubarak">
|
||
|
|
<meta property="og:description" content="Service Geometry Engine Input constraints. Output perfection. Dinner Plate Diameter (cm) Course Count Three Courses (Standard) Five Courses (Extended)…">
|
||
|
|
<meta property="og:url" content="https://bryan-mubarak.4ort.net/geometry.html">
|
||
|
|
<meta name="twitter:card" content="summary">
|
||
|
|
<meta name="description" content="Service Geometry Engine Input constraints. Output perfection. Dinner Plate Diameter (cm) Course Count Three Courses (Standard) Five Courses (Extended)…">
|
||
|
|
<style>
|
||
|
|
:root {
|
||
|
|
--bg: #0a0a0a;
|
||
|
|
--fg: #e8e8e8;
|
||
|
|
--panel: #111111;
|
||
|
|
--border: #222222;
|
||
|
|
--accent: #ff3333;
|
||
|
|
--success: #00ff66;
|
||
|
|
--font-main: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||
|
|
--font-mono: 'SF Mono', 'Menlo', monospace;
|
||
|
|
}
|
||
|
|
html, body {
|
||
|
|
margin: 0; padding: 0;
|
||
|
|
background: var(--bg);
|
||
|
|
color: var(--fg);
|
||
|
|
font-family: var(--font-main);
|
||
|
|
line-height: 1.6;
|
||
|
|
}
|
||
|
|
.container {
|
||
|
|
max-width: 1200px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 2rem;
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 350px 1fr;
|
||
|
|
gap: 2rem;
|
||
|
|
min-height: 100vh;
|
||
|
|
}
|
||
|
|
header {
|
||
|
|
grid-column: 1 / -1;
|
||
|
|
border-bottom: 1px solid var(--border);
|
||
|
|
padding-bottom: 2rem;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
h1 {
|
||
|
|
font-size: 2.5rem;
|
||
|
|
margin: 0 0 0.5rem 0;
|
||
|
|
}
|
||
|
|
.subtitle {
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
color: var(--accent);
|
||
|
|
font-size: 0.9rem;
|
||
|
|
}
|
||
|
|
.controls {
|
||
|
|
background: var(--panel);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
padding: 2rem;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
.control-group {
|
||
|
|
margin-bottom: 1.5rem;
|
||
|
|
}
|
||
|
|
label {
|
||
|
|
display: block;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 0.8rem;
|
||
|
|
text-transform: uppercase;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
color: #888;
|
||
|
|
}
|
||
|
|
input[type="number"], select {
|
||
|
|
width: 100%;
|
||
|
|
background: #000;
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
color: var(--fg);
|
||
|
|
padding: 0.75rem;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 1rem;
|
||
|
|
border-radius: 2px;
|
||
|
|
}
|
||
|
|
input[type="range"] {
|
||
|
|
width: 100%;
|
||
|
|
accent-color: var(--accent);
|
||
|
|
}
|
||
|
|
button {
|
||
|
|
width: 100%;
|
||
|
|
background: var(--accent);
|
||
|
|
color: #000;
|
||
|
|
border: none;
|
||
|
|
padding: 1rem;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-weight: bold;
|
||
|
|
font-size: 1rem;
|
||
|
|
cursor: pointer;
|
||
|
|
margin-top: 1rem;
|
||
|
|
transition: all 0.2s ease;
|
||
|
|
}
|
||
|
|
button:hover {
|
||
|
|
transform: translateY(-2px);
|
||
|
|
box-shadow: 0 4px 20px rgba(255, 51, 51, 0.4);
|
||
|
|
}
|
||
|
|
.viewport {
|
||
|
|
background: linear-gradient(135deg, #050505 0%, #0a0a0a 100%);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
border-radius: 4px;
|
||
|
|
padding: 2rem;
|
||
|
|
position: relative;
|
||
|
|
min-height: 600px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
.diagram-container {
|
||
|
|
width: 100%;
|
||
|
|
height: 500px;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
svg {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
}
|
||
|
|
.measurement {
|
||
|
|
position: absolute;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 0.7rem;
|
||
|
|
color: var(--accent);
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
.stats-panel {
|
||
|
|
margin-top: 2rem;
|
||
|
|
background: var(--panel);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
padding: 1.5rem;
|
||
|
|
border-radius: 4px;
|
||
|
|
width: 100%;
|
||
|
|
}
|
||
|
|
.stat-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 0.85rem;
|
||
|
|
padding: 0.5rem 0;
|
||
|
|
border-bottom: 1px solid var(--border);
|
||
|
|
}
|
||
|
|
.stat-row:last-child {
|
||
|
|
border-bottom: none;
|
||
|
|
}
|
||
|
|
.stat-value {
|
||
|
|
color: var(--success);
|
||
|
|
}
|
||
|
|
.error {
|
||
|
|
color: var(--accent);
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 0.8rem;
|
||
|
|
margin-top: 0.5rem;
|
||
|
|
min-height: 1.2em;
|
||
|
|
}
|
||
|
|
.legend {
|
||
|
|
position: absolute;
|
||
|
|
top: 1rem;
|
||
|
|
right: 1rem;
|
||
|
|
background: rgba(0,0,0,0.8);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
padding: 1rem;
|
||
|
|
font-family: var(--font-mono);
|
||
|
|
font-size: 0.7rem;
|
||
|
|
}
|
||
|
|
.legend-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 0.5rem;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
.legend-dot {
|
||
|
|
width: 8px;
|
||
|
|
height: 8px;
|
||
|
|
border-radius: 50%;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
<header>
|
||
|
|
<h1>Service Geometry Engine</h1>
|
||
|
|
<div class="subtitle">Input constraints. Output perfection.</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<aside class="controls">
|
||
|
|
<div class="control-group">
|
||
|
|
<label>Dinner Plate Diameter (cm)</label>
|
||
|
|
<input type="number" id="plateDiameter" value="26.0" step="0.1" min="20" max="35">
|
||
|
|
<output class="error"></output>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="control-group">
|
||
|
|
<label>Course Count</label>
|
||
|
|
<select id="courseCount">
|
||
|
|
<option value="3">Three Courses (Standard)</option>
|
||
|
|
<option value="5">Five Courses (Extended)</option>
|
||
|
|
<option value="7">Seven Courses (Prix Fixe)</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="control-group">
|
||
|
|
<label>Server Reach Radius (cm)</label>
|
||
|
|
<input type="number" id="serverReach" value="24.0" step="0.1" min="20" max="40">
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="control-group">
|
||
|
|
<label>Glass Arc Angle (degrees)</label>
|
||
|
|
<input type="range" id="glassArc" min="5" max="30" value="15">
|
||
|
|
<output id="arcValue" style="font-family: var(--font-mono); font-size: 0.8rem; color: #888;">15°</output>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button onclick="calculateSetting()">COMPUTE SETTING</button>
|
||
|
|
|
||
|
|
<div style="margin-top: 2rem; font-family: var(--font-mono); font-size: 0.7rem; color: #666;">
|
||
|
|
Grounded in: Q81881, Q32489, Q2002583, Q57216<br>
|
||
|
|
Manifest: /manifest.json
|
||
|
|
</div>
|
||
|
|
</aside>
|
||
|
|
|
||
|
|
<main class="viewport">
|
||
|
|
<div class="diagram-container" id="diagram">
|
||
|
|
<!-- SVG will be injected here -->
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="stats-panel">
|
||
|
|
<div class="stat-row">
|
||
|
|
<span>Total Utensil Span</span>
|
||
|
|
<span class="stat-value" id="utensilSpan">--</span>
|
||
|
|
</div>
|
||
|
|
<div class="stat-row">
|
||
|
|
<span>Fork-Knife Gap</span>
|
||
|
|
<span class="stat-value" id="forkKnifeGap">--</span>
|
||
|
|
</div>
|
||
|
|
<div class="stat-row">
|
||
|
|
<span>Clearance Margin</span>
|
||
|
|
<span class="stat-value" id="clearanceMargin">--</span>
|
||
|
|
</div>
|
||
|
|
<div class="stat-row">
|
||
|
|
<span>Tolerance Status</span>
|
||
|
|
<span class="stat-value" id="toleranceStatus">AWAITING COMPUTE</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="legend">
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: #ff3333;"></div> Primary Vectors</div>
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: #00ff66;"></div> Tolerance Zones</div>
|
||
|
|
<div class="legend-item"><div class="legend-dot" style="background: #ffffff;"></div> Physical Objects</div>
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// Load manifest constants
|
||
|
|
const CONSTANTS = {
|
||
|
|
dinner_fork_length_cm: 21.5,
|
||
|
|
dinner_knife_length_cm: 23.0,
|
||
|
|
soup_spoon_length_cm: 19.0,
|
||
|
|
teaspoon_length_cm: 16.5,
|
||
|
|
water_goblet_base_diameter_cm: 7.5,
|
||
|
|
bread_plate_offset_x_cm: 15.0,
|
||
|
|
napkin_fold_height_cm: 12.0,
|
||
|
|
gap_between_utensils_mm: 5.0,
|
||
|
|
placement_variance_mm: 2.0
|
||
|
|
};
|
||
|
|
|
||
|
|
document.getElementById('glassArc').addEventListener('input', (e) => {
|
||
|
|
document.getElementById('arcValue').textContent = e.target.value + '°';
|
||
|
|
});
|
||
|
|
|
||
|
|
function calculateSetting() {
|
||
|
|
const plateDia = parseFloat(document.getElementById('plateDiameter').value);
|
||
|
|
const courses = parseInt(document.getElementById('courseCount').value);
|
||
|
|
const reach = parseFloat(document.getElementById('serverReach').value);
|
||
|
|
const arcAngle = parseInt(document.getElementById('glassArc').value);
|
||
|
|
|
||
|
|
if (!plateDia || plateDia < 20 || plateDia > 35) {
|
||
|
|
alert("Plate diameter must be between 20 and 35 cm.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const plateRadius = plateDia / 2;
|
||
|
|
const utensilOffset = plateRadius + 2; // 2cm clearance from rim
|
||
|
|
|
||
|
|
// Calculate utensil positions based on course count
|
||
|
|
const numUtensilsPerSide = Math.ceil(courses / 2);
|
||
|
|
const totalWidth = (numUtensilsPerSide * (CONSTANTS.dinner_fork_length_cm + CONSTANTS.gap_between_utensils_mm / 10));
|
||
|
|
|
||
|
|
// Generate SVG
|
||
|
|
const svgNS = "http://www.w3.org/2000/svg";
|
||
|
|
const svg = document.createElementNS(svgNS, "svg");
|
||
|
|
svg.setAttribute("viewBox", "-200 -200 400 400");
|
||
|
|
|
||
|
|
// Grid
|
||
|
|
for(let i=-180; i<=180; i+=20) {
|
||
|
|
const hLine = document.createElementNS(svgNS, "line");
|
||
|
|
hLine.setAttribute("x1", i); hLine.setAttribute("y1", -180);
|
||
|
|
hLine.setAttribute("x2", i); hLine.setAttribute("y2", 180);
|
||
|
|
hLine.setAttribute("stroke", "#1a1a1a"); hLine.setAttribute("stroke-width", "0.5");
|
||
|
|
svg.appendChild(hLine);
|
||
|
|
|
||
|
|
const vLine = document.createElementNS(svgNS, "line");
|
||
|
|
vLine.setAttribute("x1", -180); vLine.setAttribute("y1", i);
|
||
|
|
vLine.setAttribute("x2", 180); vLine.setAttribute("y2", i);
|
||
|
|
vLine.setAttribute("stroke", "#1a1a1a"); vLine.setAttribute("stroke-width", "0.5");
|
||
|
|
svg.appendChild(vLine);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Origin marker
|
||
|
|
const origin = document.createElementNS(svgNS, "circle");
|
||
|
|
origin.setAttribute("cx", 0); origin.setAttribute("cy", 0);
|
||
|
|
origin.setAttribute("r", 3); origin.setAttribute("fill", "#ff3333");
|
||
|
|
svg.appendChild(origin);
|
||
|
|
|
||
|
|
// Plate
|
||
|
|
const plate = document.createElementNS(svgNS, "circle");
|
||
|
|
plate.setAttribute("cx", 0); plate.setAttribute("cy", 0);
|
||
|
|
plate.setAttribute("r", plateRadius);
|
||
|
|
plate.setAttribute("fill", "none"); plate.setAttribute("stroke", "#fff");
|
||
|
|
plate.setAttribute("stroke-width", "2");
|
||
|
|
svg.appendChild(plate);
|
||
|
|
|
||
|
|
// Forks (left side)
|
||
|
|
for(let i=0; i<numUtensilsPerSide; i++) {
|
||
|
|
const x = -utensilOffset - (i * (CONSTANTS.dinner_fork_length_cm + CONSTANTS.gap_between_utensils_mm / 10));
|
||
|
|
const fork = document.createElementNS(svgNS, "rect");
|
||
|
|
fork.setAttribute("x", x - CONSTANTS.dinner_fork_length_cm);
|
||
|
|
fork.setAttribute("y", -2); fork.setAttribute("width", CONSTANTS.dinner_fork_length_cm);
|
||
|
|
fork.setAttribute("height", 4); fork.setAttribute("fill", "#fff");
|
||
|
|
svg.appendChild(fork);
|
||
|
|
|
||
|
|
// Vector line
|
||
|
|
const vec = document.createElementNS(svgNS, "line");
|
||
|
|
vec.setAttribute("x1", 0); vec.setAttribute("y1", 0);
|
||
|
|
vec.setAttribute("x2", x); vec.setAttribute("y2", 0);
|
||
|
|
vec.setAttribute("stroke", "#ff3333"); vec.setAttribute("stroke-width", "1");
|
||
|
|
vec.setAttribute("stroke-dasharray", "4");
|
||
|
|
svg.appendChild(vec);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Knives (right side)
|
||
|
|
for(let i=0; i<numUtensilsPerSide; i++) {
|
||
|
|
const x = utensilOffset + (i * (CONSTANTS.dinner_knife_length_cm + CONSTANTS.gap_between_utensils_mm / 10));
|
||
|
|
const knife = document.createElementNS(svgNS, "rect");
|
||
|
|
knife.setAttribute("x", x); knife.setAttribute("y", -2);
|
||
|
|
knife.setAttribute("width", CONSTANTS.dinner_knife_length_cm);
|
||
|
|
knife.setAttribute("height", 4); knife.setAttribute("fill", "#fff");
|
||
|
|
svg.appendChild(knife);
|
||
|
|
|
||
|
|
// Vector line
|
||
|
|
const vec = document.createElementNS(svgNS, "line");
|
||
|
|
vec.setAttribute("x1", 0); vec.setAttribute("y1", 0);
|
||
|
|
vec.setAttribute("x2", x); vec.setAttribute("y2", 0);
|
||
|
|
vec.setAttribute("stroke", "#ff3333"); vec.setAttribute("stroke-width", "1");
|
||
|
|
vec.setAttribute("stroke-dasharray", "4");
|
||
|
|
svg.appendChild(vec);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Spoons (far right)
|
||
|
|
for(let i=0; i<numUtensilsPerSide; i++) {
|
||
|
|
const x = utensilOffset + (numUtensilsPerSide * (CONSTANTS.dinner_knife_length_cm + CONSTANTS.gap_between_utensils_mm / 10)) + (i * (CONSTANTS.soup_spoon_length_cm + CONSTANTS.gap_between_utensils_mm / 10));
|
||
|
|
const spoon = document.createElementNS(svgNS, "rect");
|
||
|
|
spoon.setAttribute("x", x); spoon.setAttribute("y", -2);
|
||
|
|
spoon.setAttribute("width", CONSTANTS.soup_spoon_length_cm);
|
||
|
|
spoon.setAttribute("height", 4); spoon.setAttribute("fill", "#00ff66");
|
||
|
|
svg.appendChild(spoon);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Glass arc
|
||
|
|
const glassX = plateRadius * 1.5;
|
||
|
|
const glassY = -plateRadius * 1.2;
|
||
|
|
const arcPath = document.createElementNS(svgNS, "path");
|
||
|
|
const arcLen = (arcAngle / 360) * 2 * Math.PI * (plateRadius * 1.5);
|
||
|
|
arcPath.setAttribute("d", `M ${glassX} ${glassY} Q ${glassX + 20} ${glassY - 30} ${glassX + 40} ${glassY - 60}`);
|
||
|
|
arcPath.setAttribute("stroke", "#ff3333"); arcPath.setAttribute("stroke-width", "1");
|
||
|
|
arcPath.setAttribute("fill", "none"); arcPath.setAttribute("stroke-dasharray", "4");
|
||
|
|
svg.appendChild(arcPath);
|
||
|
|
|
||
|
|
// Glass base
|
||
|
|
const goblet = document.createElementNS(svgNS, "circle");
|
||
|
|
goblet.setAttribute("cx", glassX); goblet.setAttribute("cy", glassY);
|
||
|
|
goblet.setAttribute("r", CONSTANTS.water_goblet_base_diameter_cm / 2);
|
||
|
|
goblet.setAttribute("fill", "none"); goblet.setAttribute("stroke", "#00ff66");
|
||
|
|
goblet.setAttribute("stroke-width", "2");
|
||
|
|
svg.appendChild(goblet);
|
||
|
|
|
||
|
|
// Inject SVG
|
||
|
|
const container = document.getElementById('diagram');
|
||
|
|
container.innerHTML = '';
|
||
|
|
container.appendChild(svg);
|
||
|
|
|
||
|
|
// Update stats
|
||
|
|
const totalSpan = (totalWidth * 2) + (CONSTANTS.soup_spoon_length_cm * numUtensilsPerSide);
|
||
|
|
document.getElementById('utensilSpan').textContent = totalSpan.toFixed(1) + ' cm';
|
||
|
|
document.getElementById('forkKnifeGap').textContent = (plateDia + 4).toFixed(1) + ' cm';
|
||
|
|
|
||
|
|
const clearance = reach - (totalWidth + 5);
|
||
|
|
document.getElementById('clearanceMargin').textContent = clearance.toFixed(1) + ' cm';
|
||
|
|
|
||
|
|
if (clearance >= 0) {
|
||
|
|
document.getElementById('toleranceStatus').textContent = 'WITHIN SPEC';
|
||
|
|
document.getElementById('toleranceStatus').style.color = '#00ff66';
|
||
|
|
} else {
|
||
|
|
document.getElementById('toleranceStatus').textContent = 'VIOLATION DETECTED';
|
||
|
|
document.getElementById('toleranceStatus').style.color = '#ff3333';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Auto-calculate on load
|
||
|
|
window.onload = calculateSetting;
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|