Show description
the “ai dev starter pack” course (from our hangout)
the “ai dev starter pack” course (from our hangout)
ai dev starter pack (course)
a cleaned-up course from your “set my friend up with ai” session: tools, workflows, and the mindset.
dark theme
offline
progress saved
hands-on
expand all
collapse all
export notes
reset
0/0 lessons done
0%
tip: everything you check off + every note is stored locally in your browser.
built from a messy transcript → turned into a usable course.
the “i just turned you into a developer” course
this is the training version of what you taught: how to think, how to set up, and how to actually ship.
it’s written like a course with modules, checklists, mini-assignments, and the “gotchas” that kept popping up.
browser vs local
export + paths
php local server
bluehost + dns
ftp/cyberduck
git + github
ollama local ai
prompting for ui
security hygiene
if you want, i can also convert this into a “course + exercises” folder layout (html + assets + checklists) — but this single file already runs anywhere.
the “ai dev starter pack” course (from our hangout)
<!--
ai-dev-starter-course.html
drop this file anywhere and open it in your browser.
everything is offline + saved in localStorage (progress + notes).
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>the “ai dev starter pack” course (from our hangout)</title>
<style>
:root{
--bg:#0b0f17;
--panel:#0f1624;
--panel2:#0c1220;
--text:#e7ecf5;
--muted:#a9b4c7;
--faint:#6f7b91;
--border:#1f2a3d;
--accent:#7c5cff;
--accent2:#22c55e;
--warn:#f59e0b;
--danger:#ef4444;
--shadow: 0 12px 40px rgba(0,0,0,.35);
--radius: 14px;
--radius2: 10px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji","Segoe UI Emoji";
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family:var(--sans);
background:
radial-gradient(1100px 600px at 15% -10%, rgba(124,92,255,.18), transparent 55%),
radial-gradient(900px 700px at 90% 10%, rgba(34,197,94,.10), transparent 55%),
radial-gradient(900px 700px at 50% 120%, rgba(245,158,11,.08), transparent 55%),
var(--bg);
color:var(--text);
line-height:1.55;
}
a{color:inherit}
.app{
display:grid;
grid-template-columns: 330px 1fr;
min-height:100vh;
}
.sidebar{
position:sticky;
top:0;
height:100vh;
padding:18px 16px;
border-right:1px solid var(--border);
background: linear-gradient(180deg, rgba(15,22,36,.92), rgba(11,15,23,.88));
backdrop-filter: blur(10px);
overflow:auto;
}
.brand{
padding:14px 14px 10px;
border:1px solid var(--border);
border-radius:var(--radius);
background: rgba(12,18,32,.7);
box-shadow: var(--shadow);
}
.brand h1{
margin:0;
font-size:14px;
letter-spacing:.5px;
font-weight:800;
text-transform:lowercase;
}
.brand .sub{
margin-top:6px;
color:var(--muted);
font-size:12px;
}
.pillrow{display:flex; gap:8px; flex-wrap:wrap; margin-top:10px;}
.pill{
border:1px solid var(--border);
padding:6px 10px;
border-radius:999px;
font-size:12px;
color:var(--muted);
background: rgba(15,22,36,.65);
}
.controls{
margin-top:14px;
display:grid;
gap:10px;
}
.search{
width:100%;
padding:10px 12px;
border-radius:12px;
border:1px solid var(--border);
background: rgba(8,12,20,.6);
color:var(--text);
outline:none;
}
.search::placeholder{color:var(--faint)}
.btnrow{display:flex; gap:10px;}
button{
appearance:none;
border:1px solid var(--border);
background: rgba(15,22,36,.7);
color:var(--text);
padding:10px 12px;
border-radius:12px;
cursor:pointer;
transition: transform .06s ease, border-color .2s ease, background .2s ease;
font-weight:700;
font-size:12px;
}
button:hover{border-color: rgba(124,92,255,.6)}
button:active{transform: translateY(1px)}
.btn-accent{
border-color: rgba(124,92,255,.6);
background: rgba(124,92,255,.12);
}
.btn-danger{
border-color: rgba(239,68,68,.55);
background: rgba(239,68,68,.10);
}
.progressWrap{
margin-top:12px;
padding:12px;
border:1px solid var(--border);
border-radius:var(--radius);
background: rgba(12,18,32,.65);
}
.progressTop{
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
font-size:12px;
color:var(--muted);
margin-bottom:8px;
}
.bar{
height:10px;
border-radius:999px;
border:1px solid var(--border);
background: rgba(8,12,20,.55);
overflow:hidden;
}
.bar > div{
height:100%;
width:0%;
background: linear-gradient(90deg, rgba(124,92,255,.9), rgba(34,197,94,.8));
border-radius:999px;
transition: width .25s ease;
}
.nav{
margin-top:14px;
display:grid;
gap:6px;
}
.nav a{
text-decoration:none;
display:flex;
gap:10px;
align-items:flex-start;
padding:10px 12px;
border-radius:12px;
border:1px solid transparent;
color:var(--muted);
transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.nav a:hover{
background: rgba(124,92,255,.08);
border-color: rgba(124,92,255,.25);
color: var(--text);
}
.nav .num{
width:24px;
height:24px;
display:grid;
place-items:center;
border-radius:8px;
border:1px solid var(--border);
color: var(--muted);
font-size:12px;
background: rgba(8,12,20,.5);
flex:0 0 auto;
margin-top:1px;
}
.nav .label{font-weight:800; font-size:12px; color:inherit}
.nav .desc{font-size:12px; color:var(--faint); margin-top:3px}
.content{
padding:26px 22px 80px;
max-width: 1040px;
}
.hero{
padding:18px 18px 16px;
border:1px solid var(--border);
border-radius: var(--radius);
background: rgba(12,18,32,.55);
box-shadow: var(--shadow);
}
.hero h2{margin:0; font-size:22px; letter-spacing:.2px}
.hero p{margin:8px 0 0; color:var(--muted)}
.grid{
margin-top:14px;
display:grid;
gap:14px;
}
.card{
border:1px solid var(--border);
border-radius: var(--radius);
background: rgba(15,22,36,.60);
box-shadow: var(--shadow);
overflow:hidden;
}
.cardHead{
padding:14px 16px;
display:flex;
gap:12px;
align-items:flex-start;
justify-content:space-between;
border-bottom:1px solid rgba(31,42,61,.65);
background: linear-gradient(180deg, rgba(15,22,36,.88), rgba(15,22,36,.55));
}
.cardHead .left{display:flex; gap:12px; align-items:flex-start}
.badge{
border:1px solid rgba(124,92,255,.35);
background: rgba(124,92,255,.10);
color: var(--text);
border-radius: 12px;
padding:6px 10px;
font-size:12px;
font-weight:900;
letter-spacing:.3px;
text-transform:lowercase;
height: fit-content;
}
.cardHead h3{margin:0; font-size:14px}
.cardHead .meta{margin-top:4px; color:var(--muted); font-size:12px}
.actions{
display:flex;
gap:10px;
align-items:center;
flex-wrap:wrap;
justify-content:flex-end;
}
.toggle{
display:flex;
gap:10px;
align-items:center;
font-size:12px;
color:var(--muted);
user-select:none;
cursor:pointer;
}
.toggle input{width:18px; height:18px}
.cardBody{padding:16px}
.cols{
display:grid;
grid-template-columns: 1.05fr .95fr;
gap:14px;
}
@media (max-width: 1040px){
.app{grid-template-columns: 1fr}
.sidebar{position:relative; height:auto; border-right:none; border-bottom:1px solid var(--border)}
.content{padding:18px 14px 80px}
.cols{grid-template-columns:1fr}
}
.kicker{
font-size:12px;
color:var(--faint);
text-transform:lowercase;
letter-spacing:.4px;
margin-bottom:8px;
}
.list{
margin:0;
padding-left:18px;
color:var(--muted);
}
.list li{margin:6px 0}
.callout{
border:1px solid rgba(34,197,94,.35);
background: rgba(34,197,94,.08);
padding:12px 12px;
border-radius: 14px;
color: var(--muted);
margin:12px 0;
}
.callout.warn{
border-color: rgba(245,158,11,.35);
background: rgba(245,158,11,.08);
}
.callout.danger{
border-color: rgba(239,68,68,.35);
background: rgba(239,68,68,.08);
}
.code{
margin:10px 0;
border:1px solid var(--border);
border-radius: 14px;
background: rgba(8,12,20,.7);
overflow:hidden;
}
.code .title{
padding:10px 12px;
border-bottom:1px solid rgba(31,42,61,.7);
color: var(--muted);
font-size:12px;
display:flex;
align-items:center;
justify-content:space-between;
gap:12px;
}
pre{
margin:0;
padding:12px;
font-family:var(--mono);
font-size:12px;
line-height:1.5;
color:#dbe6ff;
overflow:auto;
white-space:pre;
}
.tinyBtn{
padding:6px 10px;
border-radius:10px;
font-size:12px;
}
.note{
width:100%;
min-height:110px;
padding:10px 12px;
border-radius: 14px;
border:1px solid var(--border);
background: rgba(8,12,20,.55);
color: var(--text);
font-family: var(--sans);
outline:none;
resize: vertical;
}
.note::placeholder{color:var(--faint)}
details{
border:1px solid rgba(31,42,61,.75);
border-radius: 14px;
padding:10px 12px;
background: rgba(12,18,32,.45);
margin:10px 0;
}
summary{
cursor:pointer;
font-weight:900;
color: var(--text);
list-style:none;
}
summary::-webkit-details-marker{display:none}
.qa{
margin-top:10px;
color: var(--muted);
font-size:12px;
}
.footer{
margin-top:18px;
color:var(--faint);
font-size:12px;
text-align:center;
}
.tag{
display:inline-block;
padding:5px 9px;
border-radius:999px;
border:1px solid rgba(31,42,61,.9);
background: rgba(8,12,20,.45);
color:var(--muted);
font-size:12px;
margin-right:6px;
margin-top:6px;
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="brand">
<h1>ai dev starter pack (course)</h1>
<div class="sub">a cleaned-up course from your “set my friend up with ai” session: tools, workflows, and the mindset.</div>
<div class="pillrow">
<div class="pill">dark theme</div>
<div class="pill">offline</div>
<div class="pill">progress saved</div>
<div class="pill">hands-on</div>
</div>
</div>
<div class="controls">
<input id="search" class="search" placeholder="search lessons… (e.g. php, github, dns, .env, cyberduck)" />
<div class="btnrow">
<button class="btn-accent" id="expandAll">expand all</button>
<button id="collapseAll">collapse all</button>
</div>
<div class="btnrow">
<button id="exportNotes">export notes</button>
<button class="btn-danger" id="resetAll">reset</button>
</div>
<div class="progressWrap">
<div class="progressTop">
<div><span id="doneCount">0</span>/<span id="totalCount">0</span> lessons done</div>
<div id="pct">0%</div>
</div>
<div class="bar"><div id="barFill"></div></div>
<div class="footer" style="margin-top:10px">
tip: everything you check off + every note is stored locally in your browser.
</div>
</div>
</div>
<nav class="nav" id="nav">
<!-- populated by js -->
</nav>
<div class="footer">
built from a messy transcript → turned into a usable course.
</div>
</aside>
<main class="content">
<section class="hero">
<h2>the “i just turned you into a developer” course</h2>
<p>
this is the training version of what you taught: how to think, how to set up, and how to actually ship.
it’s written like a course with modules, checklists, mini-assignments, and the “gotchas” that kept popping up.
</p>
<div style="margin-top:10px">
<span class="tag">browser vs local</span>
<span class="tag">export + paths</span>
<span class="tag">php local server</span>
<span class="tag">bluehost + dns</span>
<span class="tag">ftp/cyberduck</span>
<span class="tag">git + github</span>
<span class="tag">ollama local ai</span>
<span class="tag">prompting for ui</span>
<span class="tag">security hygiene</span>
</div>
</section>
<section class="grid" id="modules">
<!-- modules inserted by js -->
</section>
<div class="footer">
if you want, i can also convert this into a “course + exercises” folder layout (html + assets + checklists) — but this single file already runs anywhere.
</div>
</main>
</div>
<script>
// -----------------------------
// course content (derived from transcript themes)
// -----------------------------
const COURSE = [
{
id: "m0",
title: "orientation: the vibe + the goal",
desc: "what you were really teaching: speed, ownership, and leverage.",
lessons: [
{
id: "m0l1",
title: "the core mission: stop renting your own workflow",
objective: "understand the ‘why’: build your own folder, your own site, your own tools, and let ai help you move fast.",
key: [
"most ‘platforms’ are just data + a theme + permissions",
"you want the ability to change your site anytime, without a middleman",
"ai is the multiplier, not the foundation"
],
steps: [
"write down: what do you do every week that feels repetitive?",
"circle the parts that are ‘data moving’ (copy/paste, export/import, renaming files)",
"circle the parts that are ‘decisions’ (what to buy, what to ship, what to post)",
"automation usually starts with data-moving; decisions come later"
],
mistakes: [
"starting with ‘build me an app’ instead of ‘what pain are we removing’",
"over-building before you have a real workflow nailed down",
"thinking you need permission to experiment (you don’t)"
],
assignment: "make a 10-line list of your weekly annoyances. pick the easiest one to automate first.",
prompt: "i want to eliminate this weekly task: [describe it]. ask me 5 questions to clarify inputs/outputs, then propose the smallest possible v1 that removes 80% of the pain.",
notePlaceholder: "your weekly annoyances list / which one you picked / why it’s easiest"
},
{
id: "m0l2",
title: "the ‘developer starter kit’ checklist",
objective: "set up the minimum stack so someone can actually build and ship.",
key: [
"one identity (email) to tie everything together",
"one place to host code (github)",
"one place to host sites (shared host or self-host)",
"one file transfer method (ftp client) + one terminal workflow"
],
steps: [
"create / confirm email access (and store recovery options)",
"create github account + log in",
"get hosting (or at least a local server workflow)",
"install: a terminal, a code editor, a password manager (seriously), and an ftp client"
],
mistakes: [
"not being able to log into email = everything breaks later",
"creating accounts but not writing down recovery + 2fa settings",
"using the same password everywhere"
],
assignment: "write your ‘source of truth’ doc: accounts + recovery + 2fa + where credentials live.",
prompt: "make me a simple ‘source of truth’ template for my accounts (email, github, hosting, domains, api keys) with sections for recovery + 2fa + notes.",
notePlaceholder: "accounts checklist / what’s done / what’s missing"
}
]
},
{
id: "m1",
title: "tools & where ai actually lives",
desc: "desktop app vs browser vs terminal: why ‘which claude’ matters.",
lessons: [
{
id: "m1l1",
title: "browser ai vs desktop ai vs terminal ai",
objective: "know what each ai can see and control — and why you got blocked before.",
key: [
"ai in the browser can interact with what’s in the browser (tabs, pages, web apps)",
"ai on your desktop (with permissions) may control files/apps locally",
"terminal-based ai can work on a local folder and run commands (best for building software)"
],
steps: [
"when something fails, ask: ‘is this in the browser, or on my computer?’",
"if it’s local: give ai a file/folder path (or open it in the tool’s workspace)",
"if it’s web: make sure you’re logged in and the page is open"
],
mistakes: [
"trying to make ‘browser ai’ read a file that’s only on your desktop",
"trying to make ‘local ai’ access a website behind login without your session",
"not noticing which window/version you’re using"
],
assignment: "pick 3 things you use daily (one web app, one local folder, one terminal task). write which ai mode handles each best.",
prompt: "i’m trying to do [task]. tell me whether it’s best done via browser ai, desktop ai, or terminal ai, and why. then give me a step-by-step.",
notePlaceholder: "your 3 daily things + which ai mode fits"
},
{
id: "m1l2",
title: "connectors & permissions (the ‘can it see this?’ test)",
objective: "stop guessing — confirm access quickly.",
key: [
"if ai can’t see it, it will ‘hallucinate’ a plan or ask you to export",
"connectors mean: the ai is allowed to access specific services",
"permissions are a feature, not a bug: protect your stuff"
],
steps: [
"open the exact page/file you want analyzed",
"ask ai: ‘repeat back the title of what you can see’",
"if it can’t, export/download to a shareable file (csv/pdf) and place it where ai can read it"
],
mistakes: [
"feeding ai an idea of the data instead of the data",
"assuming it has access to your logged-in context when it doesn’t",
"forgetting that corporate tools often block automation"
],
assignment: "write a 3-step ‘access check’ you will do every time something fails.",
prompt: "give me a quick access-debug script: 5 questions i ask ai to confirm what it can see before i waste time.",
notePlaceholder: "your personal ‘access check’ steps"
}
]
},
{
id: "m2",
title: "browser vs local: moving data like a pro",
desc: "exporting, paths, and the ‘option-right-click copy path’ move.",
lessons: [
{
id: "m2l1",
title: "the golden rule: if it’s local, export it",
objective: "understand why exporting a spreadsheet as csv solved your block.",
key: [
"web apps keep data inside the web app unless you export",
"csv is universal and ai-friendly",
"your goal is to make data portable"
],
steps: [
"in the web app: File → Export/Download → CSV",
"save it somewhere obvious (Desktop / a dedicated project folder)",
"then point your tools at that file"
],
mistakes: [
"trying to ‘scrape’ a logged-in web app when export exists",
"saving to a random folder and losing it",
"using a format that’s harder to parse (screenshots) when csv/pdf works"
],
assignment: "export one real spreadsheet you use into a project folder. name it cleanly.",
prompt: "i exported a csv from [tool]. propose a simple schema and a plan to turn this into an internal dashboard with search + filters + exports.",
notePlaceholder: "what you exported / where it lives / naming pattern"
},
{
id: "m2l2",
title: "paths: telling the computer (and ai) exactly where stuff is",
objective: "learn the ‘copy path’ technique so your tools stop guessing.",
key: [
"a ‘path’ is just the address of a file on your computer",
"accurate paths eliminate 80% of ‘it can’t find it’ issues",
"you can paste paths into terminals, scripts, and ai tools"
],
steps: [
"right-click file → (on mac) hold Option → ‘Copy as Pathname’",
"paste it into your note or terminal",
"validate the file exists before blaming the tool"
],
mistakes: [
"copying the filename only (not the full path)",
"renaming/moving files after you shared the path",
"mixing up spaces/quotes in terminal commands"
],
assignment: "copy 3 file paths (a csv, an image, a folder). paste them into your notes and label them.",
prompt: "i have this file path: [paste path]. show me 3 different ways i can use it in terminal (view, move/copy, pass into a script).",
notePlaceholder: "your 3 example paths + what each is for"
}
]
},
{
id: "m3",
title: "local dev: run a php site like it’s production",
desc: "index.php, a local server, and why this is basically the same vibe as shared hosting.",
lessons: [
{
id: "m3l1",
title: "the smallest web app: one folder + index.php",
objective: "understand the ‘one file can be a whole app’ concept you were demonstrating.",
key: [
"index.php is the default entry point on many hosts",
"you can embed html + css + js inside it",
"pair it with sqlite and you’ve got a full dashboard"
],
steps: [
"make a folder (project-name/)",
"create index.php inside it",
"run a local php server in that folder",
"open the URL in your browser"
],
mistakes: [
"not having php installed locally",
"running the server from the wrong directory",
"forgetting to refresh after changes"
],
assignment: "create a folder + index.php that prints ‘hello’. run it locally.",
prompt: "make me a single-file index.php that includes: dark ui, sqlite db auto-create, and full crud for ‘notes’ (title, body, tags).",
code: {
title: "commands: run php locally (mac)",
lang: "bash",
body:
`# install php (mac, via homebrew)
brew install php
# go to your project folder
cd /path/to/project
# start a local server
php -S 127.0.0.1:8000
# open in browser
# http://127.0.0.1:8000`
},
notePlaceholder: "what you built / what broke / what fixed it"
},
{
id: "m3l2",
title: "restart loop: edit → refresh → read errors",
objective: "use the tight feedback loop instead of guessing.",
key: [
"most progress is: make change → observe → adjust",
"errors are directions (not insults)",
"keep the terminal visible while you test"
],
steps: [
"make a tiny change",
"refresh the page",
"if it errors, read the top line first",
"fix one thing at a time"
],
mistakes: [
"changing 10 things at once",
"not checking the server output in terminal",
"stopping because the first error looks scary"
],
assignment: "intentionally break your php file once. fix it. write what the error told you.",
prompt: "i got this error: [paste it]. explain it like i’m smart but new, then give me the smallest fix.",
notePlaceholder: "error → meaning → fix"
}
]
},
{
id: "m4",
title: "hosting: domains, dns, and getting real",
desc: "bluehost style hosting, subdomains, and ‘it takes up to 48 hours’.",
lessons: [
{
id: "m4l1",
title: "domain + hosting 101: what you were explaining",
objective: "separate: domain name vs hosting vs files vs your app.",
key: [
"domain = the name (yourname.com)",
"hosting = the server space that serves files",
"your app = the folder with code",
"you can point a domain/subdomain to different folders/apps"
],
steps: [
"buy domain",
"choose hosting plan",
"log into host panel",
"locate the web root folder (often public_html)",
"upload your site files"
],
mistakes: [
"confusing domain purchase with hosting purchase",
"uploading into the wrong folder",
"expecting dns changes to be instant"
],
assignment: "draw a 4-box diagram: domain → dns → hosting → folder/app.",
prompt: "explain domain vs dns vs hosting vs app using a simple analogy, then tell me what i need to click in a typical host to upload my site.",
notePlaceholder: "your 4-box diagram in words"
},
{
id: "m4l2",
title: "dns propagation: the ‘48 hours’ reality",
objective: "know why a subdomain might not work immediately.",
key: [
"dns changes take time to propagate",
"sometimes it’s minutes; sometimes it’s longer",
"don’t panic — plan around it"
],
steps: [
"create subdomain in host panel",
"wait, then test later",
"keep notes on what you changed and when"
],
mistakes: [
"changing dns repeatedly while it’s still propagating",
"assuming the code is broken when dns is just not updated"
],
assignment: "create a subdomain plan on paper: name → purpose → folder.",
prompt: "i want subdomains for: portfolio, projects, and a private dashboard. suggest a naming scheme and folder layout.",
notePlaceholder: "subdomain plan + purpose"
}
]
},
{
id: "m5",
title: "file transfer: stop living in the web panel",
desc: "cyberduck + ftp/sftp as your ‘real’ workflow.",
lessons: [
{
id: "m5l1",
title: "ftp client (cyberduck) workflow",
objective: "move from ‘clicking around in file manager’ to a clean upload workflow.",
key: [
"ftp client = a direct connection to your server’s folders",
"drag-drop beats browser file manager",
"you’ll spend way less time fighting panels"
],
steps: [
"install an ftp client (cyberduck is common)",
"create a connection bookmark to your host",
"navigate to the correct web root",
"upload your project folder",
"refresh your domain"
],
mistakes: [
"uploading into the wrong folder (public_html vs addon domain folder)",
"overwriting files accidentally without backups",
"not keeping a local ‘gold copy’ of your site"
],
assignment: "make a local ‘deploy’ folder and practice uploading a tiny site (html only).",
prompt: "give me a safe deploy checklist so i don’t accidentally overwrite a live site.",
notePlaceholder: "your deploy checklist + what folder is your web root"
},
{
id: "m5l2",
title: "the ‘gold copy’ rule: versioned backups",
objective: "avoid the ‘i don’t know what i changed’ spiral.",
key: [
"your local project folder is the source of truth",
"server is a deployment target, not the workspace",
"git makes this painless (next module)"
],
steps: [
"keep a single ‘project’ folder on your machine",
"deploy by uploading from that folder",
"never edit production files as your main workflow"
],
mistakes: [
"editing in the host file editor and forgetting what you changed",
"no backups, then panic when something breaks"
],
assignment: "pick a folder naming scheme and stick to it (date + project).",
prompt: "give me a simple folder naming scheme for projects + client sites that scales to 100+ projects.",
notePlaceholder: "your folder naming system"
}
]
},
{
id: "m6",
title: "git + github: ‘a repo is a folder’ (but with superpowers)",
desc: "commit, push, clone, and why this replaced your old ‘rename folders’ method.",
lessons: [
{
id: "m6l1",
title: "what a repository is (in plain english)",
objective: "lock in the definition so you stop feeling lost.",
key: [
"a repo is a project folder tracked over time",
"commits are snapshots with messages",
"github is a remote place to store and share the repo"
],
steps: [
"create a repo on github",
"clone it to your machine",
"edit files",
"commit changes",
"push to github"
],
mistakes: [
"editing random folders that aren’t in the repo",
"not committing because you’re ‘not ready’ (commit anyway)",
"forgetting to push (your remote stays old)"
],
assignment: "make a repo called ‘hello-web’ and push a single html file.",
prompt: "i’m new to git. give me the minimum 6 commands i need daily, and what each means in one sentence.",
code: {
title: "git basics (minimum set)",
lang: "bash",
body:
`# first time in a project folder
git init
# connect to github repo (replace URL)
git remote add origin https://github.com/USERNAME/REPO.git
# stage changes
git add .
# commit snapshot
git commit -m "first commit"
# push to github
git push -u origin main`
},
notePlaceholder: "your first repo name + what you pushed"
},
{
id: "m6l2",
title: "github cli (gh) + homebrew",
objective: "why you installed tools with brew, and what a package manager is.",
key: [
"brew is a package manager (installs tools cleanly)",
"gh is github’s command line helper",
"most dev tools are installed via a package manager"
],
steps: [
"install homebrew (if needed)",
"brew install gh",
"gh auth login",
"use gh to create repos, open PRs, etc."
],
mistakes: [
"trying random curl scripts without understanding what they do",
"installing tools manually and losing track of versions"
],
assignment: "install gh and log in (or at least write the steps you would do).",
prompt: "explain package managers (brew/npm/pip) with examples of when i should use each.",
code: {
title: "brew + gh quick start",
lang: "bash",
body:
`# install github cli
brew install gh
# authenticate
gh auth login
# create a repo from current folder (optional)
gh repo create --source=. --public --push`
},
notePlaceholder: "what you installed with brew + any errors"
}
]
},
{
id: "m7",
title: "security basics: don’t leak yourself again",
desc: "the .env lesson, passwords, and avoiding ‘credentials in code’.",
lessons: [
{
id: "m7l1",
title: "never put passwords in code (use .env)",
objective: "move secrets out of index.php and into environment variables.",
key: [
".env is a separate file for secrets/config",
"your app reads it at runtime",
"you do not commit .env into git"
],
steps: [
"create a .env file in your project",
"load it in your app (language-specific)",
"add .env to .gitignore",
"use environment variables for credentials"
],
mistakes: [
"committing .env to github",
"hardcoding passwords in PHP",
"sharing screenshots with secrets visible"
],
assignment: "add a .env + .gitignore to your project today.",
prompt: "show me how to load a .env file in php (without frameworks) and how to structure the config safely.",
code: {
title: "minimal .gitignore",
lang: "text",
body:
`.env
*.sqlite
*.db
.DS_Store
node_modules/
vendor/`
},
notePlaceholder: "where you’ll store secrets + how you’ll avoid committing them"
},
{
id: "m7l2",
title: "password hygiene: the ‘source of truth’ doc",
objective: "keep access without reusing passwords everywhere.",
key: [
"use a password manager if possible",
"use unique passwords per service",
"turn on 2fa for email + github + hosting"
],
steps: [
"pick a password manager (or at minimum: an encrypted note)",
"set recovery email/phone",
"enable 2fa where it matters",
"store backup codes somewhere safe"
],
mistakes: [
"one password for everything",
"not knowing your email password = everything breaks",
"leaking credentials in code or screenshots"
],
assignment: "secure your email + github with 2fa. write where backup codes live.",
prompt: "create a security checklist for a solo developer running client websites (email/github/hosting).",
notePlaceholder: "your 2fa status + where backup codes are stored"
}
]
},
{
id: "m8",
title: "debugging: your browser is your x-ray",
desc: "f12, console, and reading errors instead of vibes.",
lessons: [
{
id: "m8l1",
title: "devtools: f12 → console → errors",
objective: "use the browser console to spot what’s actually happening.",
key: [
"console shows errors, logs, and network failures",
"small mistakes are normal (ports, paths, missing files)",
"debugging is a muscle you build"
],
steps: [
"open devtools (F12 / Cmd+Option+I)",
"click Console",
"refresh page and watch errors",
"fix one error at a time"
],
mistakes: [
"ignoring the console and guessing",
"fixing symptoms instead of the first error",
"not logging anything when debugging"
],
assignment: "open devtools on a site and identify 1 request + 1 console log.",
prompt: "i’m seeing this console error: [paste]. what is the most likely cause and the fastest way to confirm it?",
notePlaceholder: "one console error you saw + what it meant"
},
{
id: "m8l2",
title: "ports: the ‘5000 vs 8000’ mistake",
objective: "understand what ports are so you stop chasing ghosts.",
key: [
"a port is just a door number on your computer",
"your server listens on one port; your browser must hit that exact port",
"if the port changes, the url changes"
],
steps: [
"check what port your server printed (terminal output)",
"open the matching url in browser",
"if it fails, confirm the server is running"
],
mistakes: [
"typing the wrong port in the url",
"running 2 servers on the same port",
"closing the terminal and forgetting you stopped the server"
],
assignment: "run a local server on two different ports (8000 and 8001). hit both urls.",
prompt: "explain ports using a simple ‘doors in an apartment building’ analogy, then give me a port troubleshooting checklist.",
notePlaceholder: "ports you used + what broke + what fixed it"
}
]
},
{
id: "m9",
title: "local ai: ollama + open models (no internet required)",
desc: "why ‘local chat’ is powerful, and where it hits limits.",
lessons: [
{
id: "m9l1",
title: "ollama concept: models on your machine",
objective: "understand what you explained: local models = offline + private + cheap.",
key: [
"ollama downloads and runs models locally",
"local models can power your apps like an api",
"smaller models run faster; bigger models need more memory"
],
steps: [
"install ollama",
"pull a model",
"run a local chat",
"connect your app to the local endpoint (later)"
],
mistakes: [
"downloading huge models you can’t run",
"expecting local to be equal to top cloud models",
"forgetting that ‘private’ still needs good security if network-exposed"
],
assignment: "install ollama (or write a plan). run one model and ask it 3 questions.",
prompt: "i want to use a local model as the brain for a chatbot on my own machine. give me an architecture that keeps it private and simple.",
notePlaceholder: "what model you tried + how it felt (speed/quality)"
},
{
id: "m9l2",
title: "hosting reality: local ai is great, but sharing is harder",
objective: "lock in the constraint you mentioned: letting the public use your local model is non-trivial.",
key: [
"local is perfect for your internal tools",
"public access usually needs a hosted inference service or a server you manage",
"you can prototype local, then swap to a paid api later"
],
steps: [
"build v1 locally with a local model",
"define your interface (inputs/outputs)",
"when ready, swap the backend to a hosted model/api"
],
mistakes: [
"trying to solve internet-scale hosting before your v1 works",
"exposing local ports without understanding security"
],
assignment: "write a ‘swap plan’: local model now, hosted model later. list what must stay the same.",
prompt: "design my app so i can switch from local ollama to a hosted api later without rewriting the whole app.",
notePlaceholder: "your ‘swap plan’ notes"
}
]
},
{
id: "m10",
title: "prompting like a builder: describe what you see",
desc: "your best advice: close your eyes, narrate the ui, and the ai can design it.",
lessons: [
{
id: "m10l1",
title: "ui narration prompt (the ‘describe the screen’ method)",
objective: "learn the prompt style that produced better results for you.",
key: [
"don’t say ‘make a website’ — describe the layout you’re imagining",
"name the elements: sidebar, cards, widgets, table, search, buttons",
"tell it the vibe: dark theme, clean, fast, minimal clicks"
],
steps: [
"write a paragraph describing the layout you want",
"list the exact pages/features",
"list the data fields and actions (create/read/update/delete)",
"ask for a single-file implementation (or your preferred stack)"
],
mistakes: [
"vague prompts: ‘make it cool’ without describing structure",
"not specifying inputs/outputs and data fields",
"forgetting error states and empty states"
],
assignment: "write a 150-word ‘ui narration’ for a dashboard you want.",
prompt:
`i’m looking at a dark themed dashboard. on the left is a sidebar with these pages: [list].
the main area has a header with search, a +new button, and a status pill.
below that is a table with columns: [list]. clicking a row opens a slide-over panel on the right with edit/delete.
build this as [single file php index.php + sqlite] with full crud, seed data, and export to csv.`,
notePlaceholder: "paste your 150-word ui narration here"
},
{
id: "m10l2",
title: "data-first prompting: ‘it’s just data’",
objective: "translate business workflows into data structures and CRUD.",
key: [
"most systems = entities + fields + relationships",
"crud = the base layer; polish comes after",
"once data is clean, ai can generate views and reports fast"
],
steps: [
"write your entities (e.g., customers, orders, deliveries)",
"write fields for each entity",
"write relationships (order belongs to customer)",
"ask ai to create schema + migrations + ui"
],
mistakes: [
"skipping schema design then suffering later",
"not naming fields clearly",
"no unique ids, no timestamps, no status fields"
],
assignment: "convert one real workflow into 3 entities + fields.",
prompt: "here’s my workflow: [describe]. propose entities + sqlite schema + endpoints + admin ui screens.",
notePlaceholder: "entities + fields + relationships"
}
]
},
{
id: "m11",
title: "project blueprint: the ‘everyone gets a dashboard’ idea",
desc: "your operating system: templates, client pages, documents, ai analysis, checklists.",
lessons: [
{
id: "m11l1",
title: "template-driven dashboards (clone, customize, ship)",
objective: "turn your ‘templates folder’ approach into a repeatable system.",
key: [
"start from a known-good template",
"clone for each client/project",
"customize branding + data model + pages",
"deploy fast, iterate weekly"
],
steps: [
"build a ‘starter dashboard’ template repo",
"for each client: duplicate it, rename, adjust schema",
"ship a v1 with 3–5 core features",
"add features only when they pay rent (save time/money)"
],
mistakes: [
"reinventing your whole stack per client",
"shipping too many features before anyone uses it",
"not capturing requirements as data fields"
],
assignment: "write your starter template spec: pages, tables, and default ui components.",
prompt: "design a reusable ‘starter dashboard’ template (single-file php + sqlite) with modular sections and a clean dark theme.",
notePlaceholder: "your starter template spec"
},
{
id: "m11l2",
title: "ai-assisted documents: ‘drop files in, let ai summarize’",
objective: "create a simple intake system: upload docs → extract tasks/checklists.",
key: [
"docs become searchable knowledge when stored consistently",
"pdf/csv are easiest for tools to ingest",
"your job is to keep the pipeline clean"
],
steps: [
"create a folder per client/project",
"save all docs with clean names and dates",
"use ai to extract: requirements, checklist, next actions",
"store the outputs as markdown or in sqlite"
],
mistakes: [
"random file naming and no folder structure",
"mixing personal and client data",
"assuming ai ‘remembers’ things you never saved"
],
assignment: "pick one project folder and standardize its naming.",
prompt: "i have these files: [list]. propose a naming convention and a way to turn them into a checklist + faq + knowledge base.",
notePlaceholder: "your folder structure + naming convention"
}
]
},
{
id: "m12",
title: "next steps: a 7-day plan to lock it in",
desc: "turn the hangout into a repeatable learning sprint.",
lessons: [
{
id: "m12l1",
title: "the 7-day sprint plan",
objective: "ship a real thing in one week and build momentum.",
key: [
"day 1: accounts + tools",
"day 2: local server + hello app",
"day 3: sqlite + crud",
"day 4: github repo + commits",
"day 5: deploy to host",
"day 6: add one automation/report",
"day 7: polish + write a readme"
],
steps: [
"pick one tiny app: notes, delivery schedule, simple crm, product list",
"keep scope tight: only what you’ll actually use",
"ship every day, even if it’s small"
],
mistakes: [
"waiting for ‘perfect’ before deploying",
"adding 10 features instead of finishing 1",
"not writing down what you learned"
],
assignment: "choose your sprint project and write the one-sentence goal.",
prompt: "help me scope a 7-day sprint: ask 5 questions, then propose a daily plan with deliverables and the smallest possible v1.",
notePlaceholder: "your sprint project + one-sentence goal + daily deliverables"
},
{
id: "m12l2",
title: "the ‘teach it back’ test (how you know it clicked)",
objective: "make sure the knowledge is real (not just vibes).",
key: [
"if you can teach it back, you own it",
"explain: domain vs hosting vs folder",
"explain: repo vs folder",
"explain: why .env exists"
],
steps: [
"record a 2-minute voice memo explaining the concepts",
"note what you struggled to explain",
"turn that into your next learning task"
],
mistakes: [
"thinking you know it because you watched it once",
"not practicing retrieval (teaching forces retrieval)"
],
assignment: "write a 10-sentence explanation of the whole system to a friend.",
prompt: "i want to teach this to someone in 2 minutes. write a script that explains the whole workflow using simple analogies.",
notePlaceholder: "your 10-sentence ‘teach it back’ explanation"
}
]
}
];
// -----------------------------
// rendering
// -----------------------------
const LS_KEY = "ai_dev_course_progress_v1";
const LS_NOTES = "ai_dev_course_notes_v1";
function loadState(){
let p = {};
let n = {};
try{ p = JSON.parse(localStorage.getItem(LS_KEY) || "{}"); }catch(e){}
try{ n = JSON.parse(localStorage.getItem(LS_NOTES) || "{}"); }catch(e){}
return {progress:p, notes:n};
}
function saveProgress(progress){ localStorage.setItem(LS_KEY, JSON.stringify(progress)); }
function saveNotes(notes){ localStorage.setItem(LS_NOTES, JSON.stringify(notes)); }
function esc(s){
return (s||"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">");
}
function makeLessonHTML(mod, lesson, state){
const done = !!state.progress[lesson.id];
const note = state.notes[lesson.id] || "";
const codeBlock = lesson.code ? `
<div class="code">
<div class="title">
<div>${esc(lesson.code.title)}</div>
<button class="tinyBtn" data-copy="${esc(lesson.code.body)}">copy</button>
</div>
<pre>${esc(lesson.code.body)}</pre>
</div>` : "";
const promptBlock = lesson.prompt ? `
<details>
<summary>prompt you can copy/paste</summary>
<div class="qa">
<div class="code">
<div class="title">
<div>prompt</div>
<button class="tinyBtn" data-copy="${esc(lesson.prompt)}">copy</button>
</div>
<pre>${esc(lesson.prompt)}</pre>
</div>
</div>
</details>` : "";
const keyList = (lesson.key||[]).map(x=>`<li>${esc(x)}</li>`).join("");
const stepList = (lesson.steps||[]).map(x=>`<li>${esc(x)}</li>`).join("");
const mistakeList = (lesson.mistakes||[]).map(x=>`<li>${esc(x)}</li>`).join("");
return `
<div class="card lesson" data-lesson-title="${esc(lesson.title)} ${esc(mod.title)} ${esc((lesson.key||[]).join(" "))} ${esc((lesson.steps||[]).join(" "))}">
<div class="cardHead">
<div class="left">
<div class="badge">${esc(mod.title)}</div>
<div>
<h3 id="${esc(lesson.id)}">${esc(lesson.title)}</h3>
<div class="meta">${esc(lesson.objective || "")}</div>
</div>
</div>
<div class="actions">
<label class="toggle">
<input type="checkbox" data-done="${esc(lesson.id)}" ${done ? "checked" : ""} />
done
</label>
<button class="tinyBtn" data-scrolltop>top</button>
</div>
</div>
<div class="cardBody">
<div class="cols">
<div>
<div class="kicker">key ideas</div>
<ul class="list">${keyList}</ul>
<div class="kicker" style="margin-top:12px;">steps</div>
<ol class="list">${stepList}</ol>
${codeBlock}
</div>
<div>
<div class="callout warn">
<b>mini assignment:</b> ${esc(lesson.assignment || "")}
</div>
<div class="kicker">common mistakes</div>
<ul class="list">${mistakeList}</ul>
${promptBlock}
<div class="kicker" style="margin-top:12px;">your notes</div>
<textarea class="note" data-note="${esc(lesson.id)}" placeholder="${esc(lesson.notePlaceholder || "write notes here…")}">${esc(note)}</textarea>
<div class="callout danger" style="margin-top:12px;">
<b>security reminder:</b> don’t paste real passwords, api keys, or private client data in notes. keep secrets in a password manager or a secure vault.
</div>
</div>
</div>
</div>
</div>
`;
}
function render(){
const state = loadState();
// nav
const nav = document.getElementById("nav");
nav.innerHTML = COURSE.map((m, idx)=>{
const firstLesson = m.lessons[0]?.id || m.id;
return `
<a href="#${esc(firstLesson)}" data-nav="${esc(m.id)}">
<div class="num">${idx+1}</div>
<div>
<div class="label">${esc(m.title)}</div>
<div class="desc">${esc(m.desc)}</div>
</div>
</a>
`;
}).join("");
// modules/lessons
const modules = document.getElementById("modules");
let html = "";
COURSE.forEach(mod=>{
mod.lessons.forEach(lesson=>{
html += makeLessonHTML(mod, lesson, state);
});
});
modules.innerHTML = html;
// totals
const totalLessons = COURSE.reduce((sum,m)=>sum+m.lessons.length,0);
document.getElementById("totalCount").textContent = totalLessons;
// wire events
wire(state);
updateProgressUI();
}
function updateProgressUI(){
const state = loadState();
const total = COURSE.reduce((sum,m)=>sum+m.lessons.length,0);
const done = Object.values(state.progress).filter(Boolean).length;
const pct = total ? Math.round((done/total)*100) : 0;
document.getElementById("doneCount").textContent = done;
document.getElementById("pct").textContent = pct + "%";
document.getElementById("barFill").style.width = pct + "%";
}
function wire(state){
// done toggles
document.querySelectorAll("input[type=checkbox][data-done]").forEach(cb=>{
cb.addEventListener("change", (e)=>{
const id = e.target.getAttribute("data-done");
const st = loadState();
st.progress[id] = !!e.target.checked;
saveProgress(st.progress);
updateProgressUI();
});
});
// notes
document.querySelectorAll("textarea[data-note]").forEach(t=>{
t.addEventListener("input", (e)=>{
const id = e.target.getAttribute("data-note");
const st = loadState();
st.notes[id] = e.target.value;
saveNotes(st.notes);
});
});
// copy buttons
document.querySelectorAll("button[data-copy]").forEach(b=>{
b.addEventListener("click", async ()=>{
const txt = b.getAttribute("data-copy") || "";
try{
await navigator.clipboard.writeText(txt);
const old = b.textContent;
b.textContent = "copied";
setTimeout(()=>b.textContent = old, 800);
}catch(e){
alert("couldn't copy automatically. select the text in the code block and copy it manually.");
}
});
});
// search
const search = document.getElementById("search");
search.addEventListener("input", ()=>{
const q = (search.value || "").trim().toLowerCase();
document.querySelectorAll(".lesson").forEach(card=>{
if(!q){ card.style.display=""; return; }
const hay = (card.getAttribute("data-lesson-title") || "").toLowerCase();
card.style.display = hay.includes(q) ? "" : "none";
});
});
// expand/collapse
document.getElementById("expandAll").addEventListener("click", ()=>{
document.querySelectorAll("details").forEach(d=>d.open=true);
});
document.getElementById("collapseAll").addEventListener("click", ()=>{
document.querySelectorAll("details").forEach(d=>d.open=false);
});
// export notes
document.getElementById("exportNotes").addEventListener("click", ()=>{
const st = loadState();
const payload = {
exported_at: new Date().toISOString(),
done: st.progress,
notes: st.notes
};
const blob = new Blob([JSON.stringify(payload, null, 2)], {type:"application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "ai-dev-course-notes.json";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
});
// reset
document.getElementById("resetAll").addEventListener("click", ()=>{
const ok = confirm("reset all progress + notes? (this clears local storage for this course)");
if(!ok) return;
localStorage.removeItem(LS_KEY);
localStorage.removeItem(LS_NOTES);
render();
});
// top buttons
document.querySelectorAll("button[data-scrolltop]").forEach(b=>{
b.addEventListener("click", ()=> window.scrollTo({top:0, behavior:"smooth"}));
});
}
render();
</script>
</body>
</html>