initial commit: vibe coded compiler

This commit is contained in:
2025-08-10 17:17:01 -07:00
commit 5c1a76f223
15 changed files with 412 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
dist/

40
Makefile Normal file
View File

@@ -0,0 +1,40 @@
GO := $(shell which go)
ifeq (, $(GO))
$(error "Required Go toolchain (go) not in $(PATH).")
endif
PAGES := $(sort $(wildcard pages/*.html)) pages.yaml
.PHONY: serve build clean
build: dist/index.stamp
dist/index.stamp: $(PAGES) templates/base.gohtml templates/index.gohtml templates/print.gohtml templates/print_2up.gohtml cmd/build/main.go
@$(GO) run ./cmd/build
@mkdir -p dist
@date +%s > dist/index.stamp
serve: build
xdg-open "http://localhost:8000" || true
python -m http.server
PDF := dist/output.pdf
PDF_2UP := dist/output-2up.pdf
.PHONY: deps
deps:
@$(GO) mod tidy
.PHONY: pdf
pdf: build deps
@echo "Generating PDF with headless Chrome..."
@$(GO) run ./cmd/pdf --in dist/print.html --out $(PDF)
.PHONY: pdf-2up
pdf-2up: build deps
@echo "Generating 2-up PDF with headless Chrome..."
@$(GO) run ./cmd/pdf --in dist/print_2up.html --out $(PDF_2UP) --w 11 --h 8.5
clean:
rm -rf dist index.html

93
cmd/pdf/main.go Normal file
View File

@@ -0,0 +1,93 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"smartbar/internal/config"
"github.com/chromedp/cdproto/emulation"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
)
func findChromeExec() (string, error) {
// Honor CHROME_PATH if set
if v := os.Getenv("CHROME_PATH"); v != "" {
return v, nil
}
candidates := []string{
"google-chrome-stable",
"google-chrome",
"chromium-browser",
"chromium",
"chrome",
}
for _, name := range candidates {
if p, err := exec.LookPath(name); err == nil {
return p, nil
}
}
return "", fmt.Errorf("no Chrome/Chromium executable found; set CHROME_PATH or install chromium/google-chrome")
}
func main() {
var (
input string
output string
width float64
height float64
)
flag.StringVar(&input, "in", "dist/print.html", "input HTML file path")
flag.StringVar(&output, "out", "dist/output.pdf", "output PDF path")
flag.Float64Var(&width, "w", config.PageWidthIn, "page width in inches")
flag.Float64Var(&height, "h", config.PageHeightIn, "page height in inches")
flag.Parse()
absInput, _ := filepath.Abs(input)
url := "file://" + absInput
execPath, err := findChromeExec()
if err != nil {
log.Fatal(err)
}
opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.ExecPath(execPath), chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", true), chromedp.Flag("hide-scrollbars", true))
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
if err := chromedp.Run(ctx, chromedp.Tasks{
chromedp.Navigate(url),
chromedp.ActionFunc(func(ctx context.Context) error {
// Ensure media is print for layout (API style for v0.9.x)
if err := emulation.SetEmulatedMedia().WithMedia("print").Do(ctx); err != nil {
return err
}
// Print to PDF with exact dimensions and zero margins
wIn := width
hIn := height
params := page.PrintToPDF().
WithPaperWidth(wIn).
WithPaperHeight(hIn).
WithMarginTop(0).WithMarginBottom(0).WithMarginLeft(0).WithMarginRight(0).
WithPrintBackground(true)
buf, _, err := params.Do(ctx)
if err != nil {
return err
}
if err := os.WriteFile(output, buf, 0o644); err != nil {
return err
}
fmt.Printf("Wrote %s\n", output)
return nil
}),
}); err != nil {
log.Fatal(err)
}
}

69
css/style.css Normal file
View File

@@ -0,0 +1,69 @@
@page {
/* Browsers may ignore CSS vars in @page; PDF size is controlled by generator */
margin: 0;
}
/* Base */
html, body {
height: 100%;
}
html, body {
margin: 0;
padding: 0;
}
/* Use border-box sizing everywhere to keep dimensions predictable */
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
/* Screen preview: render a page at exact CSS inches and center it */
@media screen {
body {
background: #eeeeee;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 1rem;
box-sizing: border-box;
}
#page {
width: var(--page-w, 5.5in);
height: var(--page-h, 8.5in);
background: white;
box-shadow: 0 0 0 1px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.18);
}
}
/* Print-specific sizing to ensure exact physical dimensions */
@media print {
html, body {
width: var(--page-w, 5.5in);
height: var(--page-h, 8.5in);
margin: 0;
padding: 0;
overflow: hidden; /* Never spill to a second page */
}
#page {
width: 100%;
height: 100%;
max-width: var(--page-w, 5.5in);
max-height: var(--page-h, 8.5in);
margin: 0;
padding: 0;
overflow: hidden; /* Clip any overflow just in case */
page-break-inside: avoid; /* Legacy */
break-inside: avoid-page; /* Modern */
}
/* Avoid browser-added headers/footers if possible (user may need to disable in print dialog) */
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
box-sizing: border-box;
background: white;
}
}

18
go.mod Normal file
View File

@@ -0,0 +1,18 @@
module smartbar
go 1.21
require (
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732
github.com/chromedp/chromedp v0.9.5
)
require (
github.com/chromedp/sysutil v1.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
golang.org/x/sys v0.16.0 // indirect
)

23
go.sum Normal file
View File

@@ -0,0 +1,23 @@
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 h1:XYUCaZrW8ckGWlCRJKCSoh/iFwlpX316a8yY9IFEzv8=
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

34
index.html Normal file
View File

@@ -0,0 +1,34 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>All Pages</title>
<link rel="stylesheet" href="/css/style.css" />
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { background: #eeeeee; }
.main { display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 1rem; box-sizing: border-box; }
.thumb { display: flex; flex-direction: column; align-items: center; }
.label { font: 12px/1.2 -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; color: #555; margin: 0.25rem 0; }
.thumb iframe { width: 5.5in; height: 8.5in; border: 0; background: white; box-shadow: 0 0 0 1px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.18); }
</style>
</head>
<body>
<div class="main">
<div class="thumb">
<div class="label">cover.html</div>
<iframe src="/dist/cover.html" loading="lazy"></iframe>
</div>
<div class="thumb">
<div class="label">bouba.html</div>
<iframe src="/dist/bouba.html" loading="lazy"></iframe>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,7 @@
package config
// Centralized physical page size in inches. Change here to retarget the whole system.
var (
PageWidthIn = 5.5
PageHeightIn = 8.5
)

5
pages.yaml Normal file
View File

@@ -0,0 +1,5 @@
# locations in pages/
- cover.html
- bouba.html

9
pages/bouba.html Normal file
View File

@@ -0,0 +1,9 @@
<style>
#page {
background-color: red;
}
</style>
<h1>bouba</h1>
This is a test kiki.

8
pages/cover.html Normal file
View File

@@ -0,0 +1,8 @@
<style>
#page {
background-color: green;
}
</style>
<h1>Smart Bar</h1>
welcome to smart bar

14
templates/base.gohtml Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ .Title }}</title>
<link rel="stylesheet" href="../css/style.css" />
</head>
<body {{ .PageSizeAttr }}>
<div id="page">{{ .Content }}</div>
</body>
</html>

29
templates/index.gohtml Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>All Pages</title>
<link rel="stylesheet" href="/css/style.css" />
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { background: #eeeeee; }
.main { display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 1rem; box-sizing: border-box; }
.thumb { display: flex; flex-direction: column; align-items: center; }
.label { font: 12px/1.2 -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif; color: #555; margin: 0.25rem 0; }
.thumb iframe { width: 5.5in; height: 8.5in; border: 0; background: white; box-shadow: 0 0 0 1px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.18); }
</style>
</head>
<body>
<div class="main">
{{ range .Pages }}
<div class="thumb">
<div class="label">{{ .Name }}</div>
<iframe src="/{{ .Href }}" loading="lazy"></iframe>
</div>
{{ end }}
</div>
</body>
</html>

29
templates/print.gohtml Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Print</title>
<link rel="stylesheet" href="../css/style.css" />
<style>
/* Ensure a clean print with no gaps between pages */
@media screen { body { background: #eeeeee; } }
/* Do NOT constrain the whole document to one page; let it flow across pages */
html, body { margin: 0; padding: 0; }
@media print {
html, body { width: auto !important; height: auto !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; background: white !important; }
}
/* Each #page is a fixed-size sheet */
#page { width: var(--page-w, 5.5in); height: var(--page-h, 8.5in); background: white; box-shadow: none !important; overflow: hidden; }
#page { page-break-after: always; break-after: page; }
#page:last-child { page-break-after: auto; break-after: auto; }
</style>
</head>
<body {{ .PageSizeAttr }}>
{{ range .Pages }}
<div id="page">{{ .Content }}</div>
{{ end }}
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Print 2-Up</title>
<link rel="stylesheet" href="../css/style.css" />
<style>
/* Letter page with two half-letter pages laid out horizontally */
/* Do NOT constrain the whole document to a single sheet */
html, body { margin: 0; padding: 0; background: white; }
@media print { html, body { width: auto !important; height: auto !important; overflow: visible !important; } }
/* Each .sheet is one letter-sized page */
.sheet { display: flex; gap: 0; margin: 0; padding: 0; width: 11in; height: 8.5in; }
.cell { width: 5.5in; height: 8.5in; overflow: hidden; }
.cell > #page { width: 5.5in; height: 8.5in; box-shadow: none !important; }
.sheet { page-break-after: always; break-after: page; }
.sheet:last-child { page-break-after: auto; break-after: auto; }
</style>
</head>
<body>
{{ range .Sheets }}
<div class="sheet">
<div class="cell"><div id="page">{{ .Left.Content }}</div></div>
{{ if .Right }}<div class="cell"><div id="page">{{ .Right.Content }}</div></div>{{ end }}
</div>
{{ end }}
</body>
</html>