main
John Lauer README.html with embedded 3D walkthrough viewer, full BOM and schematic, source bundle, build outputs 23c1d5e 2mo ago
import { Molecule } from "@tsci/adom-inc.molecule"
import { UsbCReceptacle, usbCPadRowForEdge } from "./UsbCReceptacle"

// "EVAN" desk nameplate molecule. 96 x 44 mm, USB-C powered from the
// EAST (right) edge, 66 green 0603 LEDs forming a 5 x 7 dot-matrix
// bitmap of each letter. Built as an Adom molecule so it fixtures into
// the standard workcell via 4 corner machine pins.
//
// Every part is an Adom Basic Part (wiki.adom.inc/adom/adom-basic-parts):
//   LED    IN-S63BTG          green 0603, InGaN 527 nm, Vf 3.2 V @ 20 mA
//   R      CR0402-FX-3300GLF  330 Ω 1% 0402, Bourns
//   R_CC   CR0402-FX-4701GLF  4.7 kΩ 1% 0402, Bourns  (USB-C Rd)
//   C_BULK CL21A106KOQNNNG    10 µF X5R 0805, Samsung
//   C_HF   0402B104J160CT     100 nF X7R 0402, Walsin
//   J1     TYPE-C-31-M-12     wiki.adom.inc/adom/type-c-31-m-12 (LCSC C165948)

// 5x7 dot-matrix font. 1 = LED, 0 = empty. Row 0 is the top row.
// 5 columns wide (not 3) so N gets a real diagonal and V a real point.
// A 3-wide N degenerates into a blob that reads as H or M.
const FONT: Record<string, number[][]> = {
  E: [
    [1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0],
    [1, 0, 0, 0, 0],
    [1, 1, 1, 1, 0],
    [1, 0, 0, 0, 0],
    [1, 0, 0, 0, 0],
    [1, 1, 1, 1, 1],
  ],
  V: [
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 1, 0],
    [0, 0, 1, 0, 0],
  ],
  A: [
    [0, 0, 1, 0, 0],
    [0, 1, 0, 1, 0],
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
    [1, 1, 1, 1, 1],
    [1, 0, 0, 0, 1],
    [1, 0, 0, 0, 1],
  ],
  // The diagonal walks column 1 to 2 to 3 down the glyph, so the stroke
  // reads unmistakably as N rather than as H with a filled middle.
  N: [
    [1, 0, 0, 0, 1],
    [1, 1, 0, 0, 1],
    [1, 1, 0, 0, 1],
    [1, 0, 1, 0, 1],
    [1, 0, 0, 1, 1],
    [1, 0, 0, 1, 1],
    [1, 0, 0, 0, 1],
  ],
}

const TEXT = "EVAN"

const FONT_COLS = 5
const FONT_ROWS = 7
const PIXEL_PITCH = 3.0
const LETTER_GAP = 3.0
const LETTER_W = FONT_COLS * PIXEL_PITCH // 15
const LETTER_PITCH = LETTER_W + LETTER_GAP // 18
const TEXT_WIDTH =
  TEXT.length * LETTER_W + (TEXT.length - 1) * LETTER_GAP // 69 mm

// Absolute sizing so the real outer edge is a number we can trust: the
// molecule's wings would otherwise grow the board past the nominal size,
// and the USB-C mouth has to be positioned against a KNOWN edge.
// See ~/.claude/skills/adom-tsci/guides/adom-usbc.md.
const BOARD_W = 96
const BOARD_H = 44
const WING = 0.2 // nominal wing, per side, verified against the build log
const HALF_W = BOARD_W / 2 + WING // 48.2, the REAL east edge
const HALF_H = BOARD_H / 2 + WING // 22.2

// The east ~16 mm is reserved for the USB-C receptacle and its support
// parts, so the letters sit left of centre rather than centred.
const TEXT_LEFT_X = -40 // letters span x ∈ [-40, +29]
const TEXT_CENTER_Y = 1 // LED rows span y ∈ [-8, +10]

// Pad-row x for the receptacle. usbCPadRowForEdge() works back from the
// board edge through the shell so the mouth lands 0.4 mm proud: far
// enough for a cable to seat, not so far that the forward anchor holes
// fall off the board. Do not hand-tune this.
const USB_X = usbCPadRowForEdge(HALF_W, 0.4)

// IN-S63BTG is InGaN green: Vf 3.2 V typ at 20 mA, dropping to about
// 3.0 V at single-digit mA. 330 ohm from 5 V gives ~6 mA per LED.
// At 560 mcd @ 20 mA these are bright parts; 6 mA is a comfortable desk
// brightness and keeps the whole board at ~400 mA, inside what a USB-C
// source advertising Default USB Power will hand out.
const R_VALUE = "330"
const LED_COLOR = "green"
const LED_MPN = "IN-S63BTG"
const R_MPN = "CR0402-FX-3300GLF"

// ── Schematic placement ──────────────────────────────────────────────────
// tscircuit's auto-layout puts 141 symbols in ONE horizontal strip, which
// lint passes (nothing overlaps) but no human can read. So every part gets
// explicit schX/schY. Note the all-or-nothing rule: placing SOME parts
// disables auto-layout for the rest and stacks them at the origin, so once
// you start you must place everything you control.
const SCH_COLS = 8
const SCH_COL_PITCH = 8
const SCH_ROW_PITCH = 3
const SCH_LEFT = -28
const SCH_TOP = 30
/** LED/resistor pair slot for pixel `idx`. R sits left, its LED 3 units right. */
const schSlot = (idx: number) => ({
  x: SCH_LEFT + (idx % SCH_COLS) * SCH_COL_PITCH,
  y: SCH_TOP - Math.floor(idx / SCH_COLS) * SCH_ROW_PITCH,
})

type Pixel = { letterIdx: number; row: number; col: number; x: number; y: number; idx: number }

const PIXELS: Pixel[] = (() => {
  const out: Pixel[] = []
  let idx = 0
  for (let li = 0; li < TEXT.length; li++) {
    const bitmap = FONT[TEXT[li]]
    const letterX0 = TEXT_LEFT_X + li * LETTER_PITCH
    for (let row = 0; row < FONT_ROWS; row++) {
      for (let col = 0; col < FONT_COLS; col++) {
        if (!bitmap[row][col]) continue
        const x = letterX0 + col * PIXEL_PITCH + PIXEL_PITCH / 2
        const y = TEXT_CENTER_Y + (3 - row) * PIXEL_PITCH
        out.push({ letterIdx: li, row, col, x, y, idx: idx++ })
      }
    }
  }
  return out
})()

export default () => (
  <Molecule
    type="4pin"
    size={`${BOARD_W}x${BOARD_H} absolute`}
    pinType="MachinePinMediumStandard"
    wing="nominal"
    roundEdges={true}
  >
    {/* ════════════════════ TOP-LAYER SILKSCREEN ════════════════════ */}
    {/* The LEDs are the name, so the top silk stays out of their way. */}
    <silkscreentext text="E V A N" fontSize={1.3} pcbX={-30} pcbY={17.0} layer="top" />
    <silkscreentext text="his name in lights" fontSize={0.55} pcbX={-30} pcbY={15.5} layer="top" />
    <silkscreentext text="ADOM * FORT WORTH TEXAS" fontSize={0.5} pcbX={-30} pcbY={-15.0} layer="top" />

    {/* Point the user at the east-edge USB-C port */}
    <silkscreentext text="5V IN" fontSize={0.8} pcbX={36} pcbY={13.5} layer="top" />
    <silkscreentext text="USB-C >>" fontSize={0.55} pcbX={36} pcbY={12.2} layer="top" />
    <silkscreentext text="any phone charger" fontSize={0.45} pcbX={36} pcbY={-12.2} layer="top" />
    <silkscreentext text="works" fontSize={0.45} pcbX={36} pcbY={-13.2} layer="top" />

    {/* Tiny corner labels for the workcell pins */}
    <silkscreentext text="MP1" fontSize={0.4} pcbX={-HALF_W + 5} pcbY={HALF_H - 4.5} layer="top" />
    <silkscreentext text="MP2" fontSize={0.4} pcbX={HALF_W - 5} pcbY={HALF_H - 4.5} layer="top" />
    <silkscreentext text="MP3" fontSize={0.4} pcbX={HALF_W - 5} pcbY={-HALF_H + 4.5} layer="top" />
    <silkscreentext text="MP4" fontSize={0.4} pcbX={-HALF_W + 5} pcbY={-HALF_H + 4.5} layer="top" />

    {/* ════════════════════ BOTTOM-LAYER SILKSCREEN ════════════════════
        The bottom is a documentation panel. Flip the board over and it
        explains how it works, what is on it, and how to keep it alive. */}

    {/* Header band */}
    <silkscreentext layer="bottom" text="EVAN NAMEPLATE" fontSize={1.6} pcbX={0} pcbY={18.0} />
    <silkscreentext layer="bottom" text="for Evan's desk * adom.inc 2026" fontSize={0.5} pcbX={0} pcbY={16.6} />

    {/* HOW TO POWER. First thing read after unboxing */}
    <silkscreentext layer="bottom" text="HOW TO POWER IT" fontSize={0.7} pcbX={-34} pcbY={14.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="1. Grab any USB-C cable (iPhone charger works)"
      fontSize={0.45} pcbX={-34} pcbY={13.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="2. Plug one end into the RIGHT edge of this board"
      fontSize={0.45} pcbX={-34} pcbY={12.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="3. Plug the other end into a 5V USB-C source"
      fontSize={0.45} pcbX={-34} pcbY={11.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="4. EVAN lights up green - thats it"
      fontSize={0.45} pcbX={-34} pcbY={10.0} anchorAlignment="center_left" />

    {/* HOW IT WORKS */}
    <silkscreentext layer="bottom" text="HOW IT WORKS" fontSize={0.7} pcbX={-34} pcbY={8.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Each letter = 5x7 LED dot-matrix bitmap"
      fontSize={0.45} pcbX={-34} pcbY={7.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="66 green 0603 LEDs in parallel from VBUS"
      fontSize={0.45} pcbX={-34} pcbY={6.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="One 330 ohm resistor per LED -> 6mA each"
      fontSize={0.45} pcbX={-34} pcbY={5.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="No microcontroller - pure analog charm"
      fontSize={0.45} pcbX={-34} pcbY={4.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Total draw: ~400mA at 5V (2.0W)"
      fontSize={0.45} pcbX={-34} pcbY={3.0} anchorAlignment="center_left" />

    {/* BOM. Every line is an Adom Basic Part */}
    <silkscreentext layer="bottom" text="BILL OF MATERIALS (ADOM BASIC PARTS)" fontSize={0.7} pcbX={3} pcbY={14.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="D1-D66  * IN-S63BTG green 0603 * Inolux"
      fontSize={0.45} pcbX={3} pcbY={13.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="R1-R66  * CR0402-FX-3300GLF 330R 1%"
      fontSize={0.45} pcbX={3} pcbY={12.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="J1      * TYPE-C-31-M-12 * LCSC C165948"
      fontSize={0.45} pcbX={3} pcbY={11.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="R_CC1/2 * CR0402-FX-4701GLF 4K7 * USB-C Rd"
      fontSize={0.45} pcbX={3} pcbY={10.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="C_BULK  * CL21A106KOQNNNG 10uF X5R 0805"
      fontSize={0.45} pcbX={3} pcbY={9.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="C_HF    * 0402B104J160CT 100nF X7R 0402"
      fontSize={0.45} pcbX={3} pcbY={8.0} anchorAlignment="center_left" />

    {/* HOW IT WAS BUILT */}
    <silkscreentext layer="bottom" text="HOW IT WAS BUILT" fontSize={0.7} pcbX={3} pcbY={6.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Authored in tscircuit (TSX) - lib/index.tsx"
      fontSize={0.45} pcbX={3} pcbY={5.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Each pixel is one map iteration"
      fontSize={0.45} pcbX={3} pcbY={4.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Autorouted by the tscircuit autorouter"
      fontSize={0.45} pcbX={3} pcbY={3.0} anchorAlignment="center_left" />

    {/* WORKCELL FIXTURING */}
    <silkscreentext layer="bottom" text="WORKCELL FIXTURING" fontSize={0.7} pcbX={-34} pcbY={1.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="4 machine pins MP1-MP4 at the corners"
      fontSize={0.45} pcbX={-34} pcbY={0.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="No electrical function - mechanical only"
      fontSize={0.45} pcbX={-34} pcbY={-1.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Adom 96x44 absolute 4pin molecule"
      fontSize={0.45} pcbX={-34} pcbY={-2.0} anchorAlignment="center_left" />

    {/* MAINTENANCE */}
    <silkscreentext layer="bottom" text="IF A LED GOES DARK" fontSize={0.7} pcbX={3} pcbY={1.2} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Each LED is independently fused by its R"
      fontSize={0.45} pcbX={3} pcbY={0.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="One LED dying does not affect the others"
      fontSize={0.45} pcbX={3} pcbY={-1.0} anchorAlignment="center_left" />
    <silkscreentext layer="bottom" text="Replace the LED + its 330 ohm R pair"
      fontSize={0.45} pcbX={3} pcbY={-2.0} anchorAlignment="center_left" />

    {/* SAFETY */}
    <silkscreentext layer="bottom" text="Do not exceed 5.5V on VBUS - LEDs will burn"
      fontSize={0.4} pcbX={0} pcbY={-5.0} />
    <silkscreentext layer="bottom" text="ESD: handle by the edges, not the components"
      fontSize={0.4} pcbX={0} pcbY={-6.0} />

    {/* Footer credits */}
    <silkscreentext layer="bottom" text="Evan-LED-Nameplate v1.0.0"
      fontSize={0.5} pcbX={0} pcbY={-8.4} />
    <silkscreentext layer="bottom" text="Built with adom-tsci on the Adom platform"
      fontSize={0.5} pcbX={0} pcbY={-9.5} />
    {/* Apple-style provenance line */}
    <silkscreentext layer="bottom" text="Designed by Adom in Fort Worth, Texas"
      fontSize={0.5} pcbX={0} pcbY={-10.6} />
    <silkscreentext layer="bottom" text="2026-08-20"
      fontSize={0.4} pcbX={0} pcbY={-11.7} />

    {/* ════════════════════ COMPONENTS ════════════════════ */}

    {/* USB-C receptacle on the EAST (right) edge. pcbRotation 90 fires the
        mouth east; pcbX is the PAD ROW, not the shell. */}
    <UsbCReceptacle name="J1" pcbX={USB_X} pcbY={0} pcbRotation={90} schX={-24} schY={-12} />

    {/* CC pulldowns. 4K7 Rd tells the charger to turn VBUS on */}
    <resistor name="R_CC1" resistance="4.7k" footprint="0402"
      manufacturerPartNumber="CR0402-FX-4701GLF" pcbX={34} pcbY={7} schX={-8} schY={-9} />
    <resistor name="R_CC2" resistance="4.7k" footprint="0402"
      manufacturerPartNumber="CR0402-FX-4701GLF" pcbX={34} pcbY={-7} schX={-8} schY={-15} />

    {/* Bulk + HF decoupling, smooths inrush when 66 LEDs light at once */}
    <capacitor name="C_BULK" capacitance="10uF" footprint="0805"
      manufacturerPartNumber="CL21A106KOQNNNG" pcbX={34} pcbY={0} schX={2} schY={-12} />
    <capacitor name="C_HF" capacitance="100nF" footprint="0402"
      manufacturerPartNumber="0402B104J160CT" pcbX={37.5} pcbY={0} schX={8} schY={-12} />

    {/* ════════════════════ NETS ════════════════════ */}
    <net name="VBUS" />
    <net name="GND" />

    <trace from="J1.VBUS1" to="net.VBUS" />
    <trace from="J1.VBUS2" to="net.VBUS" />
    <trace from="J1.GND1" to="net.GND" />
    <trace from="J1.GND2" to="net.GND" />

    <trace from="R_CC1.pin1" to="J1.CC1" />
    <trace from="R_CC1.pin2" to="net.GND" />
    <trace from="R_CC2.pin1" to="J1.CC2" />
    <trace from="R_CC2.pin2" to="net.GND" />

    <trace from="C_BULK.pin1" to="net.VBUS" />
    <trace from="C_BULK.pin2" to="net.GND" />
    <trace from="C_HF.pin1" to="net.VBUS" />
    <trace from="C_HF.pin2" to="net.GND" />

    {/* ════════════════════ THE EVAN LED MATRIX ════════════════════ */}
    {PIXELS.map((p) => (
      <resistor
        key={`R${p.idx}`}
        name={`R${p.idx}`}
        resistance={R_VALUE}
        footprint="0402"
        manufacturerPartNumber={R_MPN}
        pcbX={p.x}
        pcbY={p.y - 1.65}
        schX={schSlot(p.idx).x}
        schY={schSlot(p.idx).y}
      />
    ))}
    {PIXELS.map((p) => (
      <led
        key={`D${p.idx}`}
        name={`D${p.idx}`}
        color={LED_COLOR}
        footprint="0603"
        manufacturerPartNumber={LED_MPN}
        pcbX={p.x}
        pcbY={p.y}
        schX={schSlot(p.idx).x + 3}
        schY={schSlot(p.idx).y}
      />
    ))}
    {PIXELS.map((p) => (
      <trace key={`tv${p.idx}`} from={`R${p.idx}.pin1`} to="net.VBUS" />
    ))}
    {PIXELS.map((p) => (
      <trace key={`tr${p.idx}`} from={`R${p.idx}.pin2`} to={`D${p.idx}.anode`} />
    ))}
    {PIXELS.map((p) => (
      <trace key={`tg${p.idx}`} from={`D${p.idx}.cathode`} to="net.GND" />
    ))}
  </Molecule>
)