diff --git a/Atomic-Blog-Generation-Pipeline/visuals/.gitignore b/Atomic-Blog-Generation-Pipeline/visuals/.gitignore new file mode 100644 index 0000000..3774a4c --- /dev/null +++ b/Atomic-Blog-Generation-Pipeline/visuals/.gitignore @@ -0,0 +1,2 @@ +.chrome-profile/ +__pycache__/ diff --git a/Atomic-Blog-Generation-Pipeline/visuals/atomic_viz.py b/Atomic-Blog-Generation-Pipeline/visuals/atomic_viz.py new file mode 100644 index 0000000..d03e88d --- /dev/null +++ b/Atomic-Blog-Generation-Pipeline/visuals/atomic_viz.py @@ -0,0 +1,191 @@ +"""Blog visuals in the Atomic "Technical Editorial" design system. + +Every figure is an HTML page rendered to PNG by headless Chrome: +ink #15171B on paper #F4F5F1, one Signal accent #3A2FF0, Bebas Neue headlines, +Hanken Grotesk body, JetBrains Mono labels, ink borders and hard offset shadows +(no blur, no gradients, dot grid on banners only). + +Figures are drawn on a 1200 px wide canvas and shown about 760 px wide in an article +(a 0.63 scale), so text is sized up: body >= 22 px, mono labels >= 19 px. + + python visuals/render.py what-is-a-local-first-notes-app +""" +import os +import subprocess + +HERE = os.path.dirname(os.path.abspath(__file__)) +PIPELINE = os.path.dirname(HERE) +COMMUNITY = os.path.dirname(PIPELINE) +WORKSPACE = os.path.dirname(COMMUNITY) +CHROME = os.environ.get("CHROME", "C:/Program Files/Google/Chrome/Application/chrome.exe") +# The app repository ships the three brand fonts. Override with ATOMIC_FONTS. +FONTS = os.environ.get("ATOMIC_FONTS", os.path.join(WORKSPACE, "Atomic-Notes-App-V0.2", "assets", "fonts")) +PUBLIC = os.path.join(COMMUNITY, "public") + + +def file_url(path): + return "file:///" + os.path.abspath(path).replace(os.sep, "/") + + +def mockup(n): + return file_url(os.path.join(PUBLIC, "mockups", f"atomic-notes-mockup-image-{n:02d}.png")) + + +ATOM = file_url(os.path.join(PUBLIC, "icons", "atom.svg")) +ICON = file_url(os.path.join(PUBLIC, "icon.png")) + +CSS = """ +@font-face { font-family: Bebas; src: url("__F__/BebasNeue-Regular.ttf"); } +@font-face { font-family: Hanken; font-weight: 400; src: url("__F__/HankenGrotesk-Regular.ttf"); } +@font-face { font-family: Hanken; font-weight: 500; src: url("__F__/HankenGrotesk-Medium.ttf"); } +@font-face { font-family: Hanken; font-weight: 700; src: url("__F__/HankenGrotesk-Bold.ttf"); } +@font-face { font-family: Mono; font-weight: 400; src: url("__F__/JetBrainsMono-Regular.ttf"); } +@font-face { font-family: Mono; font-weight: 500; src: url("__F__/JetBrainsMono-Medium.ttf"); } +:root { --ink:#15171B; --paper:#F4F5F1; --white:#FFFFFF; --surface:#EDEEE8; --signal:#3A2FF0; + --slate:#4A4D55; --line:#C6C6CB; --light:#8F88FF; --mist:#D8D6FF; --deep:#1D14A0; + --error:#BA1A1A; --error-bg:#FFDAD6; --on-error:#93000A; --high:#EB7D00; } +* { box-sizing:border-box; margin:0; padding:0; } +body { width:__W__px; height:__H__px; overflow:hidden; background:var(--paper); color:var(--ink); + font-family:Hanken, sans-serif; -webkit-font-smoothing:antialiased; } +.stage { position:relative; width:__W__px; height:__H__px; overflow:hidden; background:var(--paper); } +.stage.dots { background-image:radial-gradient(rgba(21,23,27,.12) 1.6px, transparent 1.8px); background-size:30px 30px; } +.stage.dark { background:var(--ink); color:var(--paper); } +.hdr { position:absolute; left:56px; right:56px; top:40px; display:flex; justify-content:space-between; align-items:center; } +.eyebrow { font-family:Mono; font-size:19px; letter-spacing:3px; text-transform:uppercase; color:var(--signal); } +.dark .eyebrow { color:var(--light); } +.mark { display:flex; align-items:center; gap:10px; font-family:Mono; font-size:17px; letter-spacing:2.5px; color:var(--slate); } +.dark .mark { color:rgba(244,245,241,.62); } +.mark img { width:30px; height:30px; border-radius:7px; box-shadow:2px 2px 0 var(--ink); } +.dark .mark img { box-shadow:2px 2px 0 var(--signal); } +h1.t { position:absolute; left:56px; right:56px; top:78px; font-family:Bebas; font-weight:400; font-size:62px; + line-height:.95; letter-spacing:.5px; text-transform:uppercase; } +.s { color:var(--signal); } +.dark .s { color:var(--light); } +.rule { position:absolute; left:56px; right:56px; height:0; border-top:1.5px solid var(--ink); } +.dark .rule { border-color:rgba(244,245,241,.3); } +.foot { position:absolute; left:56px; right:56px; bottom:30px; font-family:Mono; font-size:18px; letter-spacing:.6px; color:var(--slate); } +.dark .foot { color:rgba(244,245,241,.62); } +.box { position:absolute; background:var(--white); border:2.5px solid var(--ink); border-radius:9px; + box-shadow:8px 8px 0 var(--ink); padding:20px 22px; } +.box.sigshadow { box-shadow:8px 8px 0 var(--signal); } +.box.flat { box-shadow:none; } +.box.surface { background:var(--surface); } +.box.signal { background:var(--signal); color:#fff; } +.box.ink { background:var(--ink); color:var(--paper); box-shadow:8px 8px 0 var(--signal); } +.box.warn { background:var(--error-bg); border-color:var(--error); box-shadow:8px 8px 0 var(--error); color:var(--on-error); } +.dark .box { box-shadow:8px 8px 0 var(--signal); border-color:var(--paper); } +.dark .box.panel { background:rgba(244,245,241,.05); border:2px solid rgba(244,245,241,.32); box-shadow:none; color:var(--paper); } +.lbl { font-family:Mono; font-size:19px; letter-spacing:1.6px; text-transform:uppercase; color:var(--signal); } +.box.signal .lbl, .box.ink .lbl { color:var(--mist); } +.box.ink .lbl { color:var(--light); } +.dark .box.panel .lbl { color:var(--light); } +.box.warn .lbl { color:var(--error); } +.muted { color:var(--slate); } +.box.ink .muted, .dark .muted { color:rgba(244,245,241,.7); } +.box.signal .muted { color:var(--mist); } +.ttl { font-family:Bebas; font-size:40px; line-height:.95; text-transform:uppercase; margin-top:8px; } +.ttl.big { font-size:54px; } +.txt { font-size:23px; line-height:1.38; margin-top:8px; } +.num { font-family:Bebas; font-size:60px; line-height:.9; color:var(--signal); } +.box.signal .num { color:#fff; } +.chip { display:inline-block; font-family:Mono; font-size:17px; letter-spacing:1.4px; text-transform:uppercase; + padding:6px 12px; border:2px solid var(--ink); border-radius:999px; background:var(--paper); } +.chip.on { background:var(--signal); border-color:var(--signal); color:#fff; } +.chip.ink { background:var(--ink); color:var(--paper); } +.chip.bad { background:var(--error-bg); border-color:var(--error); color:var(--on-error); } +.chip.ghost { background:transparent; } +.dark .chip { border-color:var(--paper); background:transparent; color:var(--paper); } +.dark .chip.on { background:var(--signal); border-color:var(--signal); } +.phone { position:absolute; background:var(--ink); border-radius:34px; padding:9px; box-shadow:10px 10px 0 var(--signal); } +.phone.inkshadow { box-shadow:10px 10px 0 var(--ink); } +.phone img { display:block; width:100%; border-radius:26px; } +.phone .cam { position:absolute; top:16px; left:50%; width:9px; height:9px; margin-left:-4px; border-radius:50%; background:#2c2f36; } +table.d { position:absolute; border-collapse:separate; border-spacing:0; background:var(--white); border:2.5px solid var(--ink); + border-radius:9px; overflow:hidden; box-shadow:8px 8px 0 var(--ink); } +table.d th, table.d td { padding:15px 18px; border-bottom:1.5px solid var(--line); text-align:left; vertical-align:middle; } +table.d thead th { font-family:Mono; font-weight:500; font-size:18px; letter-spacing:1.4px; text-transform:uppercase; background:var(--surface); + border-bottom:2.5px solid var(--ink); } +table.d thead th.hl { background:var(--signal); color:#fff; } +table.d tbody th { font-family:Bebas; font-weight:400; font-size:30px; line-height:1; text-transform:uppercase; white-space:nowrap; } +table.d td { font-size:21px; line-height:1.3; } +table.d td.hl { background:rgba(58,47,240,.06); font-weight:500; } +table.d tr:last-child th, table.d tr:last-child td { border-bottom:0; } +.yes { color:var(--signal); font-family:Mono; font-weight:500; } +.no { color:var(--error); font-family:Mono; font-weight:500; } +.part { color:var(--slate); font-family:Mono; font-weight:500; } +svg.lines { position:absolute; inset:0; overflow:visible; } +""" + +HEAD = '
' + + +def css(w, h): + return CSS.replace("__F__", file_url(FONTS)).replace("__W__", str(w)).replace("__H__", str(h)) + + +def figure(eyebrow, title, body, foot="", w=1200, h=760, theme="", lines="", title_top=78): + """A standard figure: eyebrow + brand mark, a split headline (wrap the accent in ), content, footnote.""" + hdr = (f'{sub}
' + f'' + f'