```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Project tracker — PostgreSQL ER diagram</title>
  <style>
    :root {
      color-scheme: light;
      --background: #f5f2e9;
      --card: #fffdf8;
      --ink: #263b32;
      --muted: #66736a;
      --header: #cbdac8;
      --border: #d5dccf;
      --line: #708773;
      --badge: #e9eee3;
    }

    * { box-sizing: border-box; }

    body {
      margin: 0;
      background: var(--background);
      color: var(--ink);
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }

    main {
      max-width: 1120px;
      margin: 0 auto;
      padding: 22px 24px 32px;
    }

    h1 {
      margin: 0;
      font-size: 25px;
      font-weight: 650;
      letter-spacing: -0.7px;
    }

    .subtitle {
      margin: 6px 0 16px;
      color: var(--muted);
      font-size: 13px;
    }

    .diagram-scroll {
      overflow: auto;
      border: 1px solid var(--border);
      border-radius: 14px;
      background: #f0eee4;
    }

    .diagram-scroll:focus-visible {
      outline: 3px solid var(--line);
      outline-offset: 3px;
    }

    /*
      Cards and connectors share one fixed coordinate system.
      The entire canvas scrolls together on smaller screens.
    */
    .diagram {
      position: relative;
      width: 1040px;
      height: 624px;
    }

    .connectors {
      position: absolute;
      inset: 0;
      width: 1040px;
      height: 624px;
      pointer-events: none;
    }

    .connector {
      fill: none;
      stroke: var(--line);
      stroke-width: 2;
    }

    .connectors text {
      fill: var(--ink);
      paint-order: stroke fill;
      stroke: #f0eee4;
      stroke-width: 5px;
      stroke-linejoin: round;
    }

    .relationship-label {
      font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
      font-size: 12px;
    }

    .cardinality {
      font-size: 12px;
      font-weight: 700;
    }

    .table-card {
      position: absolute;
      width: 400px;
      overflow: hidden;
      border-radius: 9px;
      background: var(--card);
      box-shadow:
        0 0 0 1px var(--border),
        0 4px 12px rgb(38 59 50 / 5%);
    }

    .users            { left: 32px;  top: 24px;  height: 166px; }
    .project-members  { left: 608px; top: 24px;  height: 226px; }
    .tasks            { left: 32px;  top: 326px; height: 274px; }
    .projects         { left: 608px; top: 326px; height: 166px; }

    .table-card h2 {
      display: flex;
      align-items: center;
      justify-content: space-between;
      height: 44px;
      margin: 0;
      padding: 0 14px;
      background: var(--header);
      font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
      font-size: 16px;
      font-weight: 650;
    }

    .table-card h2 span {
      font-family: system-ui, sans-serif;
      font-size: 10px;
      font-weight: 500;
      letter-spacing: 0.7px;
      text-transform: uppercase;
      color: #4f6554;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      table-layout: fixed;
    }

    col.column-name { width: 46%; }
    col.column-type { width: 30%; }
    col.column-keys { width: 24%; }

    thead tr { height: 22px; }

    thead th {
      padding: 0 12px;
      background: #f4f5ed;
      color: var(--muted);
      text-align: left;
      font-size: 9px;
      line-height: 12px;
      font-weight: 600;
      letter-spacing: 0.8px;
      text-transform: uppercase;
    }

    tbody tr { height: 36px; }

    tbody th,
    tbody td {
      height: 36px;
      padding: 2px 12px;
      border-top: 1px solid #e9ece2;
      text-align: left;
      vertical-align: middle;
      font-size: 12px;
      line-height: 15px;
    }

    tbody th {
      font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
      font-weight: 500;
      white-space: nowrap;
    }

    .sql-type {
      display: block;
      font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
      white-space: nowrap;
    }

    .nullability {
      display: block;
      color: var(--muted);
      font-size: 9px;
      line-height: 11px;
      white-space: nowrap;
    }

    .keys { white-space: nowrap; }

    .badge {
      display: inline-block;
      padding: 2px 5px;
      border-radius: 4px;
      background: var(--badge);
      color: #3e5b43;
      font-size: 9px;
      font-weight: 750;
      line-height: 13px;
      vertical-align: middle;
    }

    .badge + .badge { margin-left: 3px; }
    .badge.foreign { background: #eeeadc; color: #716343; }
    .badge.unique { background: #e6e9ef; color: #516074; }

    .card-note {
      display: flex;
      align-items: center;
      height: 28px;
      margin: 0;
      padding: 0 12px;
      background: #f5f5ed;
      color: var(--muted);
      font-size: 10px;
      line-height: 14px;
    }

    .project-members .card-note {
      display: block;
      height: 52px;
      padding: 6px 12px;
      font-size: 10px;
      line-height: 13px;
    }

    .card-note strong { color: var(--ink); font-weight: 600; }

    code {
      font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
      font-size: 0.95em;
    }

    .details {
      display: grid;
      grid-template-columns: minmax(0, 1.7fr) minmax(240px, 1fr);
      gap: 28px;
      padding: 20px 2px 0;
    }

    .details h2 {
      margin: 0 0 9px;
      font-size: 14px;
      font-weight: 650;
    }

    .relationship-list {
      margin: 0;
      padding-left: 18px;
    }

    .relationship-list li {
      margin-bottom: 10px;
      padding-left: 2px;
      font-size: 12px;
      line-height: 1.6;
    }

    .relationship-list code { overflow-wrap: anywhere; }

    .relationship-list span {
      display: block;
      color: var(--muted);
    }

    .legend p {
      margin: 0 0 10px;
      font-size: 12px;
      line-height: 1.6;
      color: var(--muted);
    }

    .legend strong { color: var(--ink); }

    @media (max-width: 760px) {
      main { padding: 18px 12px 28px; }
      .details { grid-template-columns: 1fr; gap: 18px; }
      h1 { font-size: 23px; }
    }

    @media print {
      @page { size: landscape; margin: 10mm; }
      body { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
      main { max-width: none; padding: 0; }
      .diagram-scroll { overflow: visible; width: 1042px; }
      .details { width: 1040px; }
      .table-card { box-shadow: 0 0 0 1px var(--border); }
    }
  </style>
</head>
<body>
  <main>
    <header>
      <h1>Project tracker</h1>
      <p class="subtitle">
        PostgreSQL ER diagram · 4 tables · 4 declared foreign keys
      </p>
    </header>

    <div
      class="diagram-scroll"
      role="region"
      aria-label="Entity relationship diagram. Scroll horizontally on smaller screens."
      tabindex="0"
    >
      <div class="diagram">
        <!--
          Each line stops at a card boundary. Endpoint numbers describe
          the number of records at that end per record at the opposite end.
          The complete relationships are also available as text below.
        -->
        <svg
          class="connectors"
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 1040 624"
          aria-hidden="true"
          focusable="false"
        >
          <!-- users ← project_members.user_id -->
          <path class="connector" d="M 432 112 H 608"/>
          <text class="relationship-label" x="520" y="84" text-anchor="middle">user_id</text>
          <text class="cardinality" x="449" y="102" text-anchor="middle">1</text>
          <text class="cardinality" x="586" y="102" text-anchor="middle">0..*</text>

          <!-- users ← tasks.assignee_id (nullable) -->
          <path class="connector" d="M 232 190 V 326"/>
          <text class="cardinality" x="246" y="212">0..1</text>
          <text class="relationship-label" x="246" y="262">assignee_id</text>
          <text class="cardinality" x="246" y="314">0..*</text>

          <!-- projects ← project_members.project_id -->
          <path class="connector" d="M 808 250 V 326"/>
          <text class="cardinality" x="822" y="268">0..*</text>
          <text class="relationship-label" x="822" y="294">project_id</text>
          <text class="cardinality" x="822" y="319">1</text>

          <!-- projects ← tasks.project_id -->
          <path class="connector" d="M 432 414 H 608"/>
          <text class="relationship-label" x="520" y="386" text-anchor="middle">project_id</text>
          <text class="cardinality" x="454" y="404" text-anchor="middle">0..*</text>
          <text class="cardinality" x="591" y="404" text-anchor="middle">1</text>
        </svg>

        <section class="table-card users" aria-labelledby="users-heading">
          <h2 id="users-heading">users <span>Table</span></h2>
          <table aria-label="users columns">
            <colgroup>
              <col class="column-name"><col class="column-type"><col class="column-keys">
            </colgroup>
            <thead>
              <tr><th scope="col">Column</th><th scope="col">SQL type</th><th scope="col">Keys</th></tr>
            </thead>
            <tbody>
              <tr>
                <th scope="row">id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL · PK</span></td>
                <td class="keys"><span class="badge">PK</span></td>
              </tr>
              <tr>
                <th scope="row">email</th>
                <td><span class="sql-type">varchar(255)</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"><span class="badge unique">UQ</span></td>
              </tr>
            </tbody>
          </table>
          <p class="card-note">Primary key: <strong>&nbsp;id</strong></p>
        </section>

        <section class="table-card project-members" aria-labelledby="members-heading">
          <h2 id="members-heading">project_members <span>Table</span></h2>
          <table aria-label="project_members columns" aria-describedby="composite-note">
            <colgroup>
              <col class="column-name"><col class="column-type"><col class="column-keys">
            </colgroup>
            <thead>
              <tr><th scope="col">Column</th><th scope="col">SQL type</th><th scope="col">Keys</th></tr>
            </thead>
            <tbody>
              <tr>
                <th scope="row">project_id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"><span class="badge">PK¹</span><span class="badge foreign">FK</span></td>
              </tr>
              <tr>
                <th scope="row">user_id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"><span class="badge">PK¹</span><span class="badge foreign">FK</span></td>
              </tr>
              <tr>
                <th scope="row">role</th>
                <td><span class="sql-type">varchar(20)</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"></td>
              </tr>
            </tbody>
          </table>
          <p class="card-note" id="composite-note">
            <strong>¹ Composite PK: (project_id, user_id)</strong><br>
            The pair is unique; neither column is individually unique.
          </p>
        </section>

        <section class="table-card tasks" aria-labelledby="tasks-heading">
          <h2 id="tasks-heading">tasks <span>Table</span></h2>
          <table aria-label="tasks columns" aria-describedby="external-note">
            <colgroup>
              <col class="column-name"><col class="column-type"><col class="column-keys">
            </colgroup>
            <thead>
              <tr><th scope="col">Column</th><th scope="col">SQL type</th><th scope="col">Keys</th></tr>
            </thead>
            <tbody>
              <tr>
                <th scope="row">id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL · PK</span></td>
                <td class="keys"><span class="badge">PK</span></td>
              </tr>
              <tr>
                <th scope="row">project_id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"><span class="badge foreign">FK</span></td>
              </tr>
              <tr>
                <th scope="row">assignee_id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NULL allowed</span></td>
                <td class="keys"><span class="badge foreign">FK</span></td>
              </tr>
              <tr>
                <th scope="row">title</th>
                <td><span class="sql-type">varchar(200)</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"></td>
              </tr>
              <tr>
                <th scope="row">external_ticket_id</th>
                <td><span class="sql-type">varchar(80)</span><span class="nullability">NULL allowed</span></td>
                <td class="keys"></td>
              </tr>
            </tbody>
          </table>
          <p class="card-note" id="external-note">
            external_ticket_id identifies an external issue; it is not a local FK.
          </p>
        </section>

        <section class="table-card projects" aria-labelledby="projects-heading">
          <h2 id="projects-heading">projects <span>Table</span></h2>
          <table aria-label="projects columns">
            <colgroup>
              <col class="column-name"><col class="column-type"><col class="column-keys">
            </colgroup>
            <thead>
              <tr><th scope="col">Column</th><th scope="col">SQL type</th><th scope="col">Keys</th></tr>
            </thead>
            <tbody>
              <tr>
                <th scope="row">id</th>
                <td><span class="sql-type">bigint</span><span class="nullability">NOT NULL · PK</span></td>
                <td class="keys"><span class="badge">PK</span></td>
              </tr>
              <tr>
                <th scope="row">name</th>
                <td><span class="sql-type">varchar(120)</span><span class="nullability">NOT NULL</span></td>
                <td class="keys"></td>
              </tr>
            </tbody>
          </table>
          <p class="card-note">Primary key: <strong>&nbsp;id</strong></p>
        </section>
      </div>
    </div>

    <div class="details">
      <section aria-labelledby="relationships-heading">
        <h2 id="relationships-heading">Declared relationships</h2>
        <ul class="relationship-list">
          <li>
            <code>project_members.user_id → users.id</code>
            <span>Each membership has exactly 1 user; each user has 0..* memberships.</span>
          </li>
          <li>
            <code>project_members.project_id → projects.id</code>
            <span>Each membership has exactly 1 project; each project has 0..* memberships.</span>
          </li>
          <li>
            <code>tasks.project_id → projects.id</code>
            <span>Each task has exactly 1 project; each project has 0..* tasks.</span>
          </li>
          <li>
            <code>tasks.assignee_id → users.id</code>
            <span>Each task has 0..1 assignee; each user has 0..* assigned tasks.</span>
          </li>
        </ul>
      </section>

      <aside class="legend" aria-labelledby="legend-heading">
        <h2 id="legend-heading">Reading the diagram</h2>
        <p>
          <span class="badge">PK</span> Primary key, unique and non-null.
          <span class="badge foreign">FK</span> Declared foreign key.
          <span class="badge unique">UQ</span> Declared single-column UNIQUE.
          <strong>PK¹</strong> identifies members of one composite primary key.
        </p>
        <p>
          <strong>1</strong> exactly one · <strong>0..1</strong> optional one ·
          <strong>0..*</strong> zero or many.
          A number beside a card counts rows in that table for one row at the other end.
          Line labels name the referencing FK column.
        </p>
        <p>
          Required FKs reference exactly one parent; nullable
          <code>assignee_id</code> permits none. No FK column is individually
          unique, so each parent can have many referencing rows.
          No constraint requires a parent to have any children.
        </p>
        <p>
          Only declared FKs are drawn. The schema does not require an assignee
          to belong to the task’s project.
        </p>
      </aside>
    </div>
  </main>
</body>
</html>
```

Limitations: Static layout; narrow screens scroll the whole diagram. Cardinalities describe schema constraints, not existing data.
