Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back the “ai dev starter pack” course (from our hangout) Startups
Download Open
Show description 1,000 chars · Startups

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)

58,001 bytes · HTML source
<!--
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("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;");
    }

    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>