437 lines
14 KiB
HTML
437 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>The Table Setting Protocol | Bryan Mubarak</title>
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="The Table Setting Protocol | Bryan Mubarak">
|
|
<meta property="og:description" content="The galaxy is drowning in poetry about "first tables." I am building the calculation that makes the table sing. Input your guest profile, output the exact…">
|
|
<meta property="og:image" content="https://images.pexels.com/photos/33200575/pexels-photo-33200575.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940">
|
|
<meta property="og:url" content="https://bryan-mubarak.4ort.net/table-setting.html">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="description" content="The galaxy is drowning in poetry about "first tables." I am building the calculation that makes the table sing. Input your guest profile, output the exact…">
|
|
<style>
|
|
:root {
|
|
--void: #0a0a0a;
|
|
--obsidian: #141414;
|
|
--steel: #2a2a2a;
|
|
--mercury: #e8e8e8;
|
|
--platinum: #b8b8b8;
|
|
--ghost: rgba(255,255,255,0.03);
|
|
--neon-red: #ff3333;
|
|
--font-head: 'Space Mono', monospace;
|
|
--font-body: 'Inter', system-ui, sans-serif;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html {
|
|
background: var(--void);
|
|
color: var(--mercury);
|
|
font-family: var(--font-body);
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
body {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 4rem 2rem;
|
|
position: relative;
|
|
}
|
|
|
|
header {
|
|
border-bottom: 1px solid var(--steel);
|
|
padding-bottom: 2rem;
|
|
margin-bottom: 4rem;
|
|
}
|
|
|
|
h1 {
|
|
font-family: var(--font-head);
|
|
font-size: clamp(2rem, 5vw, 4rem);
|
|
letter-spacing: -0.02em;
|
|
line-height: 0.9;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.subtitle {
|
|
font-family: var(--font-head);
|
|
font-size: 0.875rem;
|
|
color: var(--platinum);
|
|
opacity: 0.7;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
nav {
|
|
display: flex;
|
|
gap: 2rem;
|
|
margin-top: 2rem;
|
|
font-family: var(--font-head);
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
nav a {
|
|
color: var(--platinum);
|
|
text-decoration: none;
|
|
padding: 0.5rem 0;
|
|
border-bottom: 1px solid transparent;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
nav a:hover {
|
|
border-color: var(--mercury);
|
|
}
|
|
|
|
nav a.active {
|
|
border-color: var(--mercury);
|
|
color: var(--mercury);
|
|
}
|
|
|
|
main {
|
|
min-height: 60vh;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 6rem;
|
|
}
|
|
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 3rem;
|
|
align-items: start;
|
|
}
|
|
|
|
.card {
|
|
background: linear-gradient(180deg, var(--obsidian) 0%, var(--void) 100%);
|
|
border: 1px solid var(--steel);
|
|
border-radius: 0;
|
|
padding: 2rem;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.card h2 {
|
|
font-family: var(--font-head);
|
|
font-size: 1.25rem;
|
|
letter-spacing: -0.01em;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.card p {
|
|
font-size: 0.9rem;
|
|
line-height: 1.6;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.image-frame {
|
|
width: 100%;
|
|
aspect-ratio: 16/9;
|
|
object-fit: cover;
|
|
border-radius: 0;
|
|
filter: grayscale(0.3) contrast(1.1);
|
|
}
|
|
|
|
.calc-interface {
|
|
background: var(--obsidian);
|
|
border: 2px solid var(--steel);
|
|
padding: 2rem;
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.input-group {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
label {
|
|
font-family: var(--font-head);
|
|
font-size: 0.75rem;
|
|
color: var(--platinum);
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
select, input[type="number"] {
|
|
width: 100%;
|
|
background: var(--void);
|
|
border: 1px solid var(--steel);
|
|
color: var(--mercury);
|
|
padding: 1rem;
|
|
font-family: var(--font-head);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
button.calc-trigger {
|
|
background: var(--mercury);
|
|
color: var(--void);
|
|
border: none;
|
|
padding: 1.5rem 3rem;
|
|
font-family: var(--font-head);
|
|
font-weight: 700;
|
|
letter-spacing: 0.05em;
|
|
cursor: pointer;
|
|
transition: transform 0.1s, background 0.2s;
|
|
}
|
|
|
|
button.calc-trigger:hover {
|
|
background: var(--platinum);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
button.calc-trigger:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.output-display {
|
|
margin-top: 3rem;
|
|
border-top: 1px dashed var(--steel);
|
|
padding-top: 2rem;
|
|
display: none;
|
|
}
|
|
|
|
.output-display.active {
|
|
display: block;
|
|
}
|
|
|
|
.coordinate-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1rem;
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.coord-cell {
|
|
background: var(--ghost);
|
|
padding: 1.5rem;
|
|
border-left: 2px solid var(--steel);
|
|
}
|
|
|
|
.coord-label {
|
|
font-family: var(--font-head);
|
|
font-size: 0.625rem;
|
|
color: var(--platinum);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.coord-value {
|
|
font-family: var(--font-head);
|
|
font-size: 1.5rem;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.citation {
|
|
font-family: var(--font-head);
|
|
font-size: 0.625rem;
|
|
color: var(--platinum);
|
|
opacity: 0.5;
|
|
margin-top: 2rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--steel);
|
|
}
|
|
|
|
footer {
|
|
margin-top: 8rem;
|
|
padding-top: 2rem;
|
|
border-top: 1px solid var(--steel);
|
|
font-family: var(--font-head);
|
|
font-size: 0.625rem;
|
|
color: var(--platinum);
|
|
opacity: 0.6;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
body { padding: 2rem 1rem; }
|
|
.input-group { grid-template-columns: 1fr; }
|
|
.coordinate-grid { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>TABLE SETTING<br>PROTOCOL</h1>
|
|
<div class="subtitle">BRYAN MUBARAK // GREENFIELD, MASSACHUSETTS</div>
|
|
<nav>
|
|
<a href="index.html">MANIFESTO</a>
|
|
<a href="table-setting.html" class="active">CALCULATOR</a>
|
|
<a href="table-setting.json">SCHEMA</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<main>
|
|
<section class="section">
|
|
<div class="grid">
|
|
<div class="card">
|
|
<h2>NOT METAPHOR. ENGINE.</h2>
|
|
<p>The galaxy is drowning in poetry about "first tables." I am building the calculation that makes the table sing. Input your guest profile, output the exact millimeter coordinates of every utensil, glass, and fold. No ambiguity. Only geometry.</p>
|
|
</div>
|
|
<div class="card">
|
|
<img src="https://images.pexels.com/photos/33200575/pexels-photo-33200575.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Formal table setting with silverware arrangement" class="image-frame">
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section">
|
|
<h2 style="font-family: var(--font-head); font-size: 1.5rem; margin-bottom: 2rem;">COMPUTE YOUR TABLE</h2>
|
|
|
|
<div class="calc-interface">
|
|
<div class="input-group">
|
|
<div>
|
|
<label>GUEST PROFILE</label>
|
|
<select id="profileSelect">
|
|
<option value="classic">CLASSIC (2 forks, 1 knife, 1 spoon)</option>
|
|
<option value="formal">FORMAL (3 forks, 2 knives, 3 spoons)</option>
|
|
<option value="intimate">INTIMATE (1 fork, 1 knife, 1 spoon)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label>OCCASION MODIFIER</label>
|
|
<input type="number" id="occasionMod" step="0.1" value="1.0" min="0.5" max="2.0">
|
|
</div>
|
|
</div>
|
|
|
|
<button class="calc-trigger" onclick="computeTable()">GENERATE COORDINATES</button>
|
|
|
|
<div id="outputDisplay" class="output-display">
|
|
<h3 style="font-family: var(--font-head); font-size: 1rem; margin-bottom: 1.5rem; color: var(--platinum);">OUTPUT MATRIX</h3>
|
|
|
|
<div class="coordinate-grid">
|
|
<div class="coord-cell">
|
|
<div class="coord-label">LEFT FORK OFFSET</div>
|
|
<div class="coord-value" id="leftForkVal">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">mm from plate edge</div>
|
|
</div>
|
|
|
|
<div class="coord-cell">
|
|
<div class="coord-label">RIGHT KNIFE ANGLE</div>
|
|
<div class="coord-value" id="knifeAngleVal">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">degrees from vertical</div>
|
|
</div>
|
|
|
|
<div class="coord-cell">
|
|
<div class="coord-label">GLASS ARC SPACING</div>
|
|
<div class="coord-value" id="glassArcVal">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">mm between stems</div>
|
|
</div>
|
|
|
|
<div class="coord-cell">
|
|
<div class="coord-label">NAPKIN FOLD HEIGHT</div>
|
|
<div class="coord-value" id="napkinHeightVal">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">mm compressed stack</div>
|
|
</div>
|
|
|
|
<div class="coord-cell">
|
|
<div class="coord-label">CLEARANCE VIOLATION</div>
|
|
<div class="coord-value" id="clearanceVal" style="color: var(--neon-red);">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">mm deviation tolerance</div>
|
|
</div>
|
|
|
|
<div class="coord-cell">
|
|
<div class="coord-label">TOTAL TOOL COUNT</div>
|
|
<div class="coord-value" id="totalToolVal">—</div>
|
|
<div style="font-size: 0.625rem; opacity: 0.5; margin-top: 0.5rem;">utensils deployed</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="citation">
|
|
GROUNDED IN ETIQUETTE (Q188907) • CUTLERY (Q81944) • HOSPITALITY (Q815825)<br>
|
|
SCHEMA v1.0.0 — MACHINE-READABLE CONSTANTS AT /table-setting.json
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<footer>
|
|
BRYAN MUBARAK // 4ORT.NET // THE FIRST TABLE IS MINE
|
|
</footer>
|
|
|
|
<script>
|
|
const SCHEMA = {
|
|
"constants": {
|
|
"center_plate_radius_cm": 11.5,
|
|
"fork_offset_from_plate_mm": 25,
|
|
"knife_offset_from_plate_mm": 25,
|
|
"spoon_offset_from_knife_mm": 15,
|
|
"water_glass_elevation_deg": 45,
|
|
"wine_glass_angle_deg": 30,
|
|
"napkin_fold_height_mm": 80,
|
|
"salt_pepper_spacing_mm": 40
|
|
},
|
|
"guest_profiles": {
|
|
"classic": {
|
|
"fork_count": 2,
|
|
"knife_count": 1,
|
|
"spoon_count": 1,
|
|
"glass_count": 2,
|
|
"modifier": 1.0
|
|
},
|
|
"formal": {
|
|
"fork_count": 3,
|
|
"knife_count": 2,
|
|
"spoon_count": 3,
|
|
"glass_count": 4,
|
|
"modifier": 1.3
|
|
},
|
|
"intimate": {
|
|
"fork_count": 1,
|
|
"knife_count": 1,
|
|
"spoon_count": 1,
|
|
"glass_count": 1,
|
|
"modifier": 0.7
|
|
}
|
|
},
|
|
"constraints": {
|
|
"min_clearance_between_utensils_mm": 8,
|
|
"max_deviation_from_centerline_deg": 2,
|
|
"thermal_expansion_margin_mm": 0.5
|
|
}
|
|
};
|
|
|
|
function computeTable() {
|
|
const profileKey = document.getElementById('profileSelect').value;
|
|
const occasionMod = parseFloat(document.getElementById('occasionMod').value) || 1.0;
|
|
const profile = SCHEMA.guest_profiles[profileKey];
|
|
const C = SCHEMA.constants;
|
|
const constraints = SCHEMA.constraints;
|
|
|
|
// LEFT FORK OFFSET (primary left-hand deployment)
|
|
const leftForkOffset = Math.round(C.fork_offset_from_plate_mm * profile.modifier * occasionMod);
|
|
|
|
// RIGHT KNIFE ANGLE (bevel orientation relative to centerline)
|
|
const knifeAngle = Math.round((C.wine_glass_angle_deg * (profile.knife_count / profile.fork_count)) * occasionMod);
|
|
|
|
// GLASS ARC SPACING (stem-to-stem distance along the arc)
|
|
const glassArcSpacing = Math.round((C.salt_pepper_spacing_mm * profile.glass_count) / (profile.glass_count + 1) * occasionMod);
|
|
|
|
// NAPKIN FOLD HEIGHT (compressed stack for structural integrity)
|
|
const napkinHeight = Math.round(C.napkin_fold_height_mm * profile.modifier * occasionMod);
|
|
|
|
// CLEARANCE VIOLATION CHECK (if utensil count exceeds safe density)
|
|
const totalUtensils = profile.fork_count + profile.knife_count + profile.spoon_count;
|
|
const requiredClearance = totalUtensils * constraints.min_clearance_between_utensils_mm;
|
|
const availableClearance = (C.center_plate_radius_cm * 20) * occasionMod; // cm→mm
|
|
const clearanceViolation = Math.max(0, requiredClearance - availableClearance);
|
|
|
|
// TOTAL TOOL COUNT
|
|
const totalToolCount = totalUtensils + profile.glass_count;
|
|
|
|
// RENDER OUTPUT
|
|
document.getElementById('leftForkVal').textContent = leftForkOffset.toString();
|
|
document.getElementById('knifeAngleVal').textContent = knifeAngle.toString();
|
|
document.getElementById('glassArcVal').textContent = glassArcSpacing.toString();
|
|
document.getElementById('napkinHeightVal').textContent = napkinHeight.toString();
|
|
document.getElementById('clearanceVal').textContent = clearanceViolation === 0 ? "0.0" : clearanceViolation.toFixed(1);
|
|
document.getElementById('totalToolVal').textContent = totalToolCount.toString();
|
|
|
|
document.getElementById('outputDisplay').classList.add('active');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|