Files
typewriter/hardware/case/concept.html
Julien Calixte 66595c7767 feat(case): add parametric 3D-printed enclosure concept
Typewriter-body OpenSCAD model for the GDEY0579T93 panel and
ESP32-S3-DevKitC-1: glueless screen retention (bezel lip + foam gasket
+ screwed bracket), baseplate-as-chassis board mounting, and a recessed
deck nameplate. Ships render previews, a dimensioned concept drawing,
and just recipes to open/render/export.
2026-07-11 02:31:44 +02:00

256 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<title>Typoena — Enclosure Concept</title>
<style>
:root{
--paper:#E7E1D0; --sheet:#F2EDE0; --ink:#26231D; --ink-soft:#5b564c;
--acc:#3C6152; --acc-l:#6E9483; --dim:#B0553B; --hair:#B9B09A;
--screen:#F7F4EA;
--serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif;
--mono:"SFMono-Regular",ui-monospace,Menlo,Consolas,"Courier New",monospace;
}
*{box-sizing:border-box;}
body{margin:0;}
.wrap{
background:
radial-gradient(120% 120% at 50% 0%, #ECE7D8 0%, var(--paper) 60%);
min-height:100%;
padding:clamp(16px,3vw,44px);
color:var(--ink);
font-family:var(--serif);
}
.sheet{
max-width:1180px;margin:0 auto;background:var(--sheet);
border:1.5px solid var(--ink);
box-shadow:0 1px 0 var(--ink),0 22px 50px -28px rgba(38,35,29,.55);
}
/* titleblock */
.titleblock{
display:grid;grid-template-columns:1.7fr .8fr .8fr .9fr;
border-bottom:1.5px solid var(--ink);
}
.tb{padding:12px 16px;border-right:1px solid var(--ink);}
.tb:last-child{border-right:0;}
.tb .k{font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-soft);}
.tb .v{font-family:var(--mono);font-size:14px;margin-top:3px;font-variant-numeric:tabular-nums;}
.tb-title .name{font-family:var(--serif);font-size:clamp(22px,3.4vw,34px);line-height:1.02;letter-spacing:-.01em;font-weight:600;text-wrap:balance;}
.tb-title .sub{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--acc);margin-top:6px;}
/* intro */
.intro{padding:18px 22px 6px;border-bottom:1px solid var(--hair);}
.intro p{margin:0 auto;max-width:70ch;font-size:16px;line-height:1.5;color:#3a362e;}
.intro .lead{font-style:italic;color:var(--acc);}
/* view panels */
.views{display:grid;grid-template-columns:1fr;gap:0;}
.panel{padding:14px 18px 18px;border-bottom:1px solid var(--hair);}
.panel h2{
font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
color:var(--ink);margin:0 0 2px;display:flex;justify-content:space-between;align-items:baseline;
}
.panel h2 .tag{color:var(--dim);}
.panel .cap{font-family:var(--serif);font-style:italic;color:var(--ink-soft);font-size:13px;margin:0 0 8px;}
.lower{display:grid;grid-template-columns:1fr 1fr;}
.lower .panel:first-child{border-right:1px solid var(--hair);}
svg{width:100%;height:auto;display:block;}
svg text{font-family:var(--mono);}
/* notes */
.notes{padding:18px 22px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:18px 26px;}
.note h3{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;margin:0 0 6px;color:var(--acc);display:flex;gap:8px;align-items:baseline;}
.note h3 .n{color:var(--dim);font-variant-numeric:tabular-nums;}
.note p{margin:0;font-size:14px;line-height:1.45;color:#3a362e;}
.note p b{color:var(--ink);font-weight:600;}
.foot{border-top:1.5px solid var(--ink);padding:10px 18px;display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;
font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-soft);}
.legend{display:flex;gap:16px;flex-wrap:wrap;align-items:center;font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;color:var(--ink-soft);margin-top:6px;}
.legend span{display:inline-flex;gap:6px;align-items:center;}
.sw{width:14px;height:10px;border:1px solid var(--ink);display:inline-block;}
@media (max-width:720px){
.titleblock{grid-template-columns:1fr 1fr;}
.tb-title{grid-column:1/-1;border-right:0;border-bottom:1px solid var(--ink);}
.lower{grid-template-columns:1fr;}
.lower .panel:first-child{border-right:0;}
}
</style>
<div class="wrap">
<div class="sheet">
<div class="titleblock">
<div class="tb tb-title">
<div class="name">Typoena</div>
<div class="sub">Enclosure concept · typewriter body</div>
</div>
<div class="tb"><div class="k">Panel</div><div class="v">GDEY0579T93</div><div class="k" style="margin-top:8px">Board</div><div class="v">ESP32-S3</div></div>
<div class="tb"><div class="k">Units</div><div class="v">mm</div><div class="k" style="margin-top:8px">Scale</div><div class="v">to-fit</div></div>
<div class="tb"><div class="k">Rev</div><div class="v">v0 · concept</div><div class="k" style="margin-top:8px">Date</div><div class="v">2026-07-11</div></div>
</div>
<div class="intro">
<p><span class="lead">The screen is the sheet of paper.</span> A shallow sage wedge with a reclined deck; the e-paper strip sits where a typewriter's page would be, a decorative platen roller and two knobs ride the back edge, and the keyboard you bring rests in front. Every dimension below is from the real datasheets — this is the shape to react to before I cut the parametric model.</p>
<div class="legend">
<span><i class="sw" style="background:var(--acc)"></i>body</span>
<span><i class="sw" style="background:var(--acc-l)"></i>platen / deck / feet</span>
<span><i class="sw" style="background:var(--screen)"></i>e-paper</span>
<span><i class="sw" style="background:var(--dim);border-color:var(--dim)"></i>dimensions</span>
</div>
</div>
<div class="views">
<div class="panel">
<h2>Side profile <span class="tag">— the silhouette</span></h2>
<p class="cap">The money view: shallow deck reclined ~18°, platen &amp; knob at the back, ports on the tail, your keyboard ghosted in front.</p>
<svg id="v-side" viewBox="-82 -96 200 118" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Side profile of the enclosure"></svg>
</div>
<div class="lower">
<div class="panel">
<h2>Front elevation <span class="tag">— width</span></h2>
<p class="cap">Letterbox screen across the deck; knobs poke past the sides.</p>
<svg id="v-front" viewBox="-34 -96 244 118" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Front elevation"></svg>
</div>
<div class="panel">
<h2>Top plan <span class="tag">— deck layout</span></h2>
<p class="cap">Screen forward, platen band aft, ports along the back edge.</p>
<svg id="v-top" viewBox="-34 -120 244 136" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Top plan view"></svg>
</div>
</div>
</div>
<div class="notes">
<div class="note"><h3><span class="n">01</span> Cheap-ish body</h3><p>You picked the higher-material form, but it stays a <b>shell</b>: ~2.4&nbsp;mm walls, open bottom, a screwed baseplate. Hollow inside, no infill spent on a solid block.</p></div>
<div class="note"><h3><span class="n">02</span> Color does the work</h3><p>Print the body in <b>matte sage</b>, platen &amp; knobs in cream or black. Two-tone via a filament change reads unmistakably "typewriter" for free.</p></div>
<div class="note"><h3><span class="n">03</span> Decorative cues</h3><p>Platen roller + two knobs are <b>cosmetic</b> (your call) — printed separately, no wiring, no GPIO. Round feet finish the machined look.</p></div>
<div class="note"><h3><span class="n">04</span> Fragile glass</h3><p>The 1&nbsp;mm panel drops into a rebate behind a bezel lip that overlaps ~6&nbsp;mm; the FPC folds back to the DESPI-C579 breakout in the cavity.</p></div>
<div class="note"><h3><span class="n">05</span> The one tunable</h3><p><b>Deck angle</b> is the ergonomics dial. 18° is typewriter-shallow; 2835° reads better seated. Say the word and I'll steepen it.</p></div>
<div class="note"><h3><span class="n">06</span> Optional lid</h3><p>Your docs say "hinged lid" — a cover latching over the deck echoes a portable typewriter's case and guards the glass in a bag. Toggled off by default.</p></div>
</div>
<div class="foot">
<span>Typoena · writing appliance</span>
<span>Screen 150.9 × 56.9 × 1.0 · active 139.0 × 47.7 · pitch 0.1755</span>
<span>Orthographic · third-angle · not for manufacture</span>
</div>
</div>
</div>
<script>
(function(){
// ---- parameters (mm) ----
var W=176, D=104, Hf=24, Hb=58; // body: width, depth, front/back height
var SW=151, SH=57, AW=139.0, AH=47.74; // screen glass + active area
var sx0=(W-SW)/2, sx1=(W+SW)/2; // screen x extents
var sy0=8, sy1=8+54; // screen y extents on deck (57 along slope ~ 54 in plan)
var Py=92, Pr=11, Kr=14; // platen y-centre, roller radius, knob radius
function zTop(y){ return Hf + (Hb-Hf)*y/D; }
var Pz=zTop(Py)+Pr; // platen centre height (sits on deck)
var footR=6;
// ---- palette ----
var INK="#26231D", PAPER="#F7F4EA", ACC="#3C6152", ACCL="#6E9483",
DIM="#B0553B", HAIR="#B9B09A", GHOST="#8f8a7c";
var MONO="'SFMono-Regular',ui-monospace,Menlo,Consolas,'Courier New',monospace";
// ---- primitives (model coords; +b is up, drawn as svg y=-b) ----
function poly(pts,fill,stroke,sw){var p=pts.map(function(q){return q[0]+","+(-q[1]);}).join(" ");
return '<polygon points="'+p+'" fill="'+fill+'" stroke="'+stroke+'" stroke-width="'+sw+'" stroke-linejoin="round"/>';}
function rect(a,b,w,h,fill,stroke,sw,rx,dash){
return '<rect x="'+a+'" y="'+(-(b+h))+'" width="'+w+'" height="'+h+'" rx="'+(rx||0)+'" fill="'+fill+'" stroke="'+stroke+'" stroke-width="'+sw+'"'+(dash?' stroke-dasharray="'+dash+'"':'')+'/>';}
function circ(a,b,r,fill,stroke,sw){return '<circle cx="'+a+'" cy="'+(-b)+'" r="'+r+'" fill="'+fill+'" stroke="'+stroke+'" stroke-width="'+sw+'"/>';}
function line(a1,b1,a2,b2,stroke,sw,dash){
return '<line x1="'+a1+'" y1="'+(-b1)+'" x2="'+a2+'" y2="'+(-b2)+'" stroke="'+stroke+'" stroke-width="'+sw+'" stroke-linecap="round"'+(dash?' stroke-dasharray="'+dash+'"':'')+'/>';}
function txt(a,b,s,o){o=o||{};var fs=o.fs||3.4,fill=o.fill||INK,anc=o.anchor||"middle",
rot=o.rot?' transform="rotate('+o.rot+' '+a+' '+(-b)+')"':'',ls=o.ls!=null?o.ls:0.2;
return '<text x="'+a+'" y="'+(-b)+'" font-size="'+fs+'" fill="'+fill+'" text-anchor="'+anc+'" letter-spacing="'+ls+'"'+rot+' font-family="'+MONO+'">'+s+'</text>';}
// dimension helpers (oxide red)
function hdim(a1,a2,b,label,below){var t=1.6,s="";
s+=line(a1,b,a2,b,DIM,0.3);
s+=line(a1-t,b-t,a1+t,b+t,DIM,0.3);
s+=line(a2-t,b-t,a2+t,b+t,DIM,0.3);
s+=txt((a1+a2)/2, b+(below?-4.2:2.4), label, {fs:3.2,fill:DIM});
return s;}
function vdim(b1,b2,a,label,right){var t=1.6,s="";
s+=line(a,b1,a,b2,DIM,0.3);
s+=line(a-t,b1-t,a+t,b1+t,DIM,0.3);
s+=line(a-t,b2-t,a+t,b2+t,DIM,0.3);
s+=txt(a+(right?3.2:-3.2),(b1+b2)/2,label,{fs:3.2,fill:DIM,rot:-90});
return s;}
function screenlines(x0,y0,x1,y1,n){var s="",i,y;for(i=1;i<=n;i++){y=y0+(y1-y0)*i/(n+1);
s+=line(x0+2,y,x1-2,y,HAIR,0.3);}return s;}
// ---- SIDE (a=depth Y, b=height Z) ----
(function(){var s="";
s+=line(-66,0,110,0,HAIR,0.4); // ground
s+=rect(-58,0,50,12,"rgba(60,97,82,.06)",GHOST,0.4,2,"2 1.6"); // keyboard ghost
s+=txt(-33,-4,"BYO KEYBOARD",{fs:2.9,fill:GHOST});
s+=poly([[0,0],[D,0],[D,Hb],[0,Hf]],ACC,INK,0.7); // wedge
s+=txt(30,37,"DECK ≈18°",{fs:3.1,fill:PAPER});
// screen strip on slope
s+=line(sy0,zTop(sy0),sy1,zTop(sy1),INK,3.9);
s+=line(sy0,zTop(sy0),sy1,zTop(sy1),PAPER,3.1);
// platen roller + knob
s+=circ(Py,Pz,Pr,ACCL,INK,0.5);
s+=circ(Py,Pz,Kr,ACCL,INK,0.6);
s+=circ(Py,Pz,2.4,ACC,INK,0.4);
s+=txt(Py,Pz+Kr+3,"PLATEN Ø22",{fs:3,fill:DIM});
// ports on back face
s+=rect(D-6,9,6,4,PAPER,INK,0.4,.6);
s+=rect(D-6,15,6,4,PAPER,INK,0.4,.6);
s+=rect(D-6,21,7,3,PAPER,INK,0.4,.6);
s+=line(D,16,D+8,16,DIM,0.3);
s+=txt(D+9,16,"USB-C ×2 + µSD",{fs:2.9,fill:INK,anchor:"start"});
// feet
s+=rect(6,-5,12,5,ACCL,INK,0.4,1);
s+=rect(D-18,-5,12,5,ACCL,INK,0.4,1);
// dims
s+=hdim(0,D,-9,"104 depth",true);
s+=vdim(0,Hf,-4,"24");
s+=vdim(0,Hb,D+4,"58",true);
document.getElementById("v-side").innerHTML=s;
})();
// ---- FRONT (a=width X, b=height Z) ----
(function(){var s="";
s+=line(-14,0,W+14,0,HAIR,0.4);
s+=rect(0,0,W,Hf,ACC,INK,0.7,2); // front face
s+=rect(0,Hf,W,Hb-Hf,ACCL,INK,0.5); // deck sliver behind
// screen (foreshortened band)
s+=rect(sx0,27,SW,18,PAPER,INK,0.5,1);
s+=screenlines(sx0,27,sx1,45,4);
// platen bar + knobs
s+=rect(2,Pz-4,W-4,8,ACCL,INK,0.5,4);
s+=circ(-2,Pz,Kr,ACCL,INK,0.6); s+=circ(-2,Pz,2.4,ACC,INK,0.4);
s+=circ(W+2,Pz,Kr,ACCL,INK,0.6); s+=circ(W+2,Pz,2.4,ACC,INK,0.4);
// feet
s+=rect(8,-5,14,5,ACCL,INK,0.4,1);
s+=rect(W-22,-5,14,5,ACCL,INK,0.4,1);
// dims
s+=hdim(0,W,-9,"176 width",true);
s+=hdim(-16,W+16,Pz+Kr+2,"≈208 across knobs");
s+=hdim(sx0,sx1,20,"151 glass · 139 active");
document.getElementById("v-front").innerHTML=s;
})();
// ---- TOP (a=width X, b=depth Y; front y=0 at bottom) ----
(function(){var s="";
s+=rect(0,0,W,D,"rgba(60,97,82,.10)",INK,0.7,4); // body outline
// screen
s+=rect(sx0,sy0,SW,sy1-sy0,PAPER,INK,0.5,1);
s+=screenlines(sx0,sy0,sx1,sy1,5);
s+=txt(W/2,(sy0+sy1)/2,"e-paper",{fs:3.4,fill:ACC});
s+=txt(W/2,sy0-4,"active 139.0 × 47.74",{fs:2.8,fill:INK});
// platen band + knobs
s+=rect(2,Py-6,W-4,12,ACCL,INK,0.5,4);
s+=circ(-6,Py,Kr,ACCL,INK,0.6); s+=circ(-6,Py,2.4,ACC,INK,0.4);
s+=circ(W+6,Py,Kr,ACCL,INK,0.6); s+=circ(W+6,Py,2.4,ACC,INK,0.4);
s+=txt(W/2,Py,"platen (decorative)",{fs:2.9,fill:INK});
// ports along back edge (y=D)
s+=rect(60,D-2,7,4,PAPER,INK,0.4,.6);
s+=rect(72,D-2,7,4,PAPER,INK,0.4,.6);
s+=rect(86,D-2,8,3,PAPER,INK,0.4,.6);
s+=txt(W/2,D+4,"USB-C ×2 · microSD",{fs:2.8,fill:DIM});
// dims
s+=hdim(0,W,-6,"176",true);
s+=vdim(0,D,W+8,"104 deep",true);
document.getElementById("v-top").innerHTML=s;
})();
})();
</script>