# resume.github.com User Guide

`resume.github.com` is a small, single-page web app that turns any public GitHub
profile into a printable, share-friendly résumé. It runs entirely in the
browser: a tiny JavaScript file pulls public data from the GitHub REST API,
fills in a Mustache template, and renders the result as a styled CV that
prints cleanly to PDF or paper.

This guide is written for the **end user** — the person who wants to generate
their own résumé page or look up someone else's. It covers the hosted site
(no install required), the optional self-hosted setup, and every visible
behaviour of the front-end. For internals, deployment, and security notes,
see the companion Administrator & Developer Guide in the same folder.

> [!NOTE]
> The repository is dormant — the `f599075` commit this guide is pinned to
> dates from August 2016. The site itself (`https://resume.github.com`)
> still works because it depends only on the public GitHub REST API, jQuery,
> and Mustache. There is no backend, no database, and no account to create.

## 1. Quick Start

The fastest way to use this project is to **visit the hosted site** — there
is nothing to install, and no account to create.

### Generate your résumé in 30 seconds

1. Open the hosted site: `https://resume.github.com`.
2. Sign in to GitHub if you aren't already, and **star the
   `resume/resume.github.com` repository**. This is the project's opt-in
   mechanism — without a star on that repo, the app will refuse to render
   your profile.
3. Append your GitHub username as a query string: `https://resume.github.com/?yourusername`.
4. Wait a moment while the page fetches your profile, repos, merged pull
   requests, and organisations from `api.github.com`.
5. When the résumé renders, click the **Print** button at the top of the
   page (or use your browser's `Ctrl+P` / `Cmd+P`) to save it as a PDF or
   send it to a physical printer.

> [!TIP]
> The query-string syntax is unusual — there is no `?username=` key, just
> the bare value: `?octocat`, `?defunkt`, `?torvalds`. The first key from
> `window.location.search` is read as the username regardless of its name,
> so `?u=octocat` and `?octocat=anything` both resolve to the user
> `octocat` or `u` respectively.

### What the page actually shows

A rendered résumé pulls from your **public** GitHub data only and contains:

- **Identity block** — display name (falls back to login), avatar, location,
  email, blog URL, and join date. If you joined GitHub in the first four
  months (Feb–May 2008) the page tags you as an *Early Adopter*.
- **Status label** — a single line ranging from *Inactive GitHub user* to
  *Passionate GitHub user*, computed from a weighted score of your
  repositories, gists, followers, and following counts.
- **Top languages** — up to nine programming languages you publish in,
  ranked by repository count, displayed with a percentage of your total.
- **Featured projects** — up to five non-fork repositories ranked by
  popularity (watchers + forks), with name, year span, language, and
  description. Emoji shortcodes (`:rocket:`) are rewritten to GitHub's
  20×20 emoji images.
- **Open-source contributions** — repositories where your merged pull
  requests live, ranked by the number of merged PRs you authored.
- **Organisations** — up to five organisations you belong to (subject to a
  shared item-count limit).
- **Print button** — calls `window.print()` for a clean, paginated output.

> [!IMPORTANT]
> The site fetches data via JSONP (`?callback=?`). That means the unauthenticated
> public GitHub API is used, with its global rate limit of 60 requests per
> hour per IP address. Multiple résumé loads from the same network can hit
> the limit and trip the *API limit reached* screen.

### Looking up someone else

The same query-string mechanism works for any GitHub user or organisation.
Try `https://resume.github.com/?defunkt` to see one of GitHub's earliest
employees, or substitute any login. The look-up is read-only — no data
about the visitor is sent to GitHub beyond what the unauthenticated API
calls naturally include.

## 2. Installation

Most users do not need to install anything. The project is a public website,
and the only "installation" required is **starring the repository in your
GitHub account** so the opt-in check passes.

### Path A — Use the hosted site (recommended)

| Step | Action | Where |
|------|--------|-------|
| 1 | Sign in to GitHub | `https://github.com` |
| 2 | Star `resume/resume.github.com` | `https://github.com/resume/resume.github.com` (click the **Star** button) |
| 3 | Open your résumé | `https://resume.github.com/?yourusername` |

That's the entire installation. There is no package to download, no CLI to
install, and no configuration file to edit.

> [!TIP]
> If you ever want to **opt out** of being rendered, simply unstar the
> repository. The next time someone visits `https://resume.github.com/?yourusername`
> they will see the *opt out* screen instead of your résumé.

### Path B — Run a local copy

You only need to run the project locally if you want to embed it in your
own site, customise the styling, or develop a fork. The repository is a
plain static site plus a one-line Rack runner; any static file server
works.

#### Prerequisites

- [x] A copy of the source: `git clone https://github.com/resume/resume.github.com.git`
- [x] One of: Ruby + Rack, Python, Node.js, or any other static file server
- [x] A modern browser with JavaScript enabled

#### Run with Rack (matches the upstream `config.ru`)

```bash
cd resume.github.com
gem install rack
rackup config.ru
```

```text
[2026-04-29 10:00:00] INFO  WEBrick 1.7.0
[2026-04-29 10:00:00] INFO  ruby 3.2.2 (2023-03-30) [x86_64-linux]
[2026-04-29 10:00:00] INFO  WEBrick::HTTPServer#start: pid=12345 port=9292
```

Then visit `http://localhost:9292/?yourusername`.

#### Run with Python's built-in server

```bash
cd resume.github.com
python3 -m http.server 8000
```

```text
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
```

Then visit `http://localhost:8000/?yourusername`.

#### Run with Node's `http-server`

```bash
cd resume.github.com
npx http-server -p 8080
```

Then visit `http://localhost:8080/?yourusername`.

> [!WARNING]
> If you load `index.html` directly via `file://` (double-clicking the
> file in your file manager), the `$.ajax` calls that fetch
> `views/index.html`, `views/resume.html`, etc. will be blocked by your
> browser's same-origin / `file://` policy. **Always serve over HTTP**,
> even for local development.

### Verifying a local install

Once your server is running, verify each piece in turn:

```bash
curl -s http://localhost:9292/index.html | head -5
```

```text
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Resume</title>
```

If the index page loads but no résumé appears when you append `?<username>`,
open your browser's developer console — the front end logs JavaScript
errors via `console.log(err)` from inside the `$(document).ready` block.

## 3. Configuration

The repository ships with **no configuration file**, no environment
variables, and no command-line flags. Everything that the page does is
controlled by either the URL query string or hard-coded constants in
`js/githubresume.js`.

### Runtime input — the URL

| Input | Where | What it does |
|-------|-------|--------------|
| Bare query string (e.g. `?octocat`) | After `?` in the URL | Sets the GitHub username to render. The first key parsed from `window.location.search` becomes the value of `username`. Empty query routes to the home page. |
| `https://` vs `http://` | Page protocol | Determines whether Google Analytics loads from `https://ssl.google-analytics.com` or `http://www.google-analytics.com`. |

> [!NOTE]
> The URL parser walks `window.location.search` once and assigns the
> result to `urlParams[0]`. It does NOT support multiple parameters, named
> keys (`?username=…`), or fragments. Any extra `&key=value` pairs are
> ignored.

### Hard-coded knobs (require editing source)

If you self-host and want to tweak behaviour, these are the only values
worth knowing about. They live near the top of `js/githubresume.js` or
inside the `run()` function. See the Admin Guide for the full reference.

| Constant | Default | What it controls |
|----------|---------|------------------|
| `trackerId` | `'UA-21222559-1'` | Google Analytics property. Set to falsy to disable analytics entirely. |
| `maxItems` | `5` | Maximum featured projects shown. Also reused as the soft cap for organisations. |
| `maxLanguages` | `9` | Maximum number of languages displayed in the *Languages* block. |
| `COEF_REPOS`, `COEF_GISTS`, `COEF_FOLLOWERS`, `COEF_FOLLOWING` | `2`, `0.25`, `0.5`, `0.25` | Weighting coefficients for the user-status score. |
| `FIRST_STEP` … `FIFTH_STEP` | `0`, `5`, `20`, `50`, `150` | Score thresholds that map to the *Inactive* → *Passionate* labels. |

> [!TIP]
> If you fork the project for your own portfolio, the very first edit
> almost everyone makes is replacing `trackerId` with their own Google
> Analytics ID, or removing the analytics block at the bottom of
> `js/githubresume.js` entirely.

### Stylesheets

Two CSS files control the appearance:

- `css/resume.css` — on-screen styling, layout, colours.
- `css/print.css` — applied via `@media print`, strips colour and chrome
  for clean PDF output.

You can edit these directly without touching JavaScript. The HTML view
fragments in `views/*.html` are Mustache templates rendered into a single
`<div id="resume">` host element, so any CSS selector that targets a
template's class will style the rendered output.

## 4. Core Usage

This section walks through every visible behaviour and screen in the app,
in the order a real visitor encounters them.

### 4.1 Landing page (no username)

When you load the site with no query string, the JavaScript falls into the
`home()` branch and AJAX-loads `views/index.html` into the `#resume`
container. This is the default landing experience — a brief introduction
plus an input field for the username.

### 4.2 Rendering a résumé

When the URL contains a query string, the `run()` function executes a
multi-step pipeline:

1. **Star check** — synchronous `GET https://api.github.com/users/<user>/starred?per_page=100`,
   paginated 100 at a time. The walker stops as soon as it sees
   `resume/resume.github.com` in `repo.full_name`. If the user has not
   starred the repo, `views/opt_out.html` is shown.
2. **User profile** — `GET https://api.github.com/users/<user>?callback=?`
   (JSONP). Populates the identity block, computes *Early Adopter* and
   the *Status Label*.
3. **Repositories** — `GET https://api.github.com/users/<user>/repos?per_page=100&callback=?`,
   paginated. Forks are filtered out. The remainder are sorted by
   `watchers + forks`, top five rendered into `#jobs` from `views/job.html`,
   and the language histogram populated.
4. **Merged pull requests** — `GET https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>&per_page=100&callback=?`.
   Results are bucketed by `repository_url`, sorted by count, and rendered
   into `#contrib-jobs` from `views/contrib.html`.
5. **Organisations** — `GET https://api.github.com/users/<user>/orgs?callback=?`.
   Up to `maxItems` organisations are rendered into `#orgs` from
   `views/org.html`.
6. **Title update** — the document title becomes `<Name>'s Résumé`.
7. **Print binding** — the `#actions #print` element is wired to call
   `window.print()`.

> [!NOTE]
> The user profile, repositories, merged PRs, and organisations calls all
> run **in parallel** as soon as the star check passes. Render order is
> therefore not deterministic — the first block that returns from GitHub
> populates first.

### 4.3 The status label

Your résumé header includes a single status string. It is derived from
this formula:

```text
score = (public_repos    * 2.00)
      + (public_gists    * 0.25)
      + (followers       * 0.50)
      + (following       * 0.25)
      + (early_adopter ? 1 : 0)
      + (location && email && blog ? 1 : 0)
```

The score then maps to a label:

| Score | Label |
|------:|-------|
| `0` | Inactive GitHub user |
| `0 < s ≤ 5` | Newbie GitHub user |
| `5 < s ≤ 20` | Regular GitHub user |
| `20 < s ≤ 50` | Advanced GitHub user |
| `50 < s ≤ 150` | Enthusiastic GitHub user |
| `> 150` | Passionate GitHub user |

The *Early Adopter* tag is awarded if your account was created in 2008
through May (or earlier — the original code includes a `since <= '2007'`
branch even though GitHub launched in February 2008).

### 4.4 Featured projects

The repos table renders five rows. Each row pulls from one of your
non-fork repositories, ranked by `watchers + forks` (so popularity, not
recency, drives the order). The shape of each row is:

| Field | Source |
|-------|--------|
| Name | `repo.name` |
| Date | `repo.created_at` year, optionally `created – pushed` if different |
| Language | `repo.language` |
| Description | `repo.description` (with `:emoji:` shortcodes rewritten to GitHub's emoji images) |
| Watchers | `repo.watchers` (label switches between `star` and `stars`) |
| Forks | `repo.forks` (label switches between `fork` and `forks`) |
| Homepage | `repo.homepage` |

> [!NOTE]
> "Watchers" here is the legacy GitHub field — what's now called *Stars*.
> The label `'stars'` is shown on rendered output, but the underlying
> JSON property is `watchers`.

### 4.5 Languages bar

The languages histogram counts each non-fork repo's `language` field once,
sorts by frequency, takes the top nine, and renders each as
`<a href="https://github.com/search?q=user:<name>&l=<encoded language>">…</a>`
with a percentage of the total. Languages are grouped into rows of three
in the rendered HTML.

### 4.6 Open-source contributions

This block uses GitHub's search-issues endpoint to count merged PRs
authored by the user, grouped by repository. Each row links to the
`/commits?author=<user>` URL on the upstream project.

> [!WARNING]
> The search endpoint is heavily rate-limited (separate from the regular
> 60-per-hour limit) and search results are capped at 1000 items globally.
> Users with very large contribution histories may see incomplete
> contribution lists.

### 4.7 Organisations

The orgs section is rendered from `views/org.html`. It uses the legacy
`/orgs` endpoint without a per-page parameter and shares the `itemCount`
counter with the projects block — so if a user already has five featured
projects rendered, organisations are silently truncated.

### 4.8 Printing

The `#print` button at the top of the page calls `window.print()`. Modern
browsers will offer "Save as PDF" as a destination. The `css/print.css`
sheet handles the print-only layout (fewer colours, removal of
interactive chrome, page-friendly typography).

> [!TIP]
> For the cleanest printable output, set your browser's print preview to
> *No headers and footers* and *Default* margins. Chrome and Edge both
> respect `@media print` and `@page` directives shipped in `print.css`.

## 5. Common Tasks

A handful of recipes for things visitors and self-hosters routinely need.

### 5.1 Save your résumé as a PDF

1. Visit `https://resume.github.com/?yourusername` and wait for the page
   to fully render.
2. Click the **Print** button or press `Ctrl+P` (`Cmd+P` on macOS).
3. In the print dialog, choose **Save as PDF** as the destination.
4. Check **Background graphics** is enabled if you want the avatar and
   colour accents to survive the export.
5. Save. Done.

### 5.2 Share your résumé as a link

There is nothing to share other than the URL itself. Send
`https://resume.github.com/?yourusername` in an email, in your LinkedIn
*Featured* section, or as a `vCard` URL. The page is fully public — no
authentication is required for the recipient.

> [!TIP]
> If the recipient does not have a GitHub account, they can still view
> your résumé. The star check applies only to the **subject** of the
> résumé (the user being looked up), not the visitor.

### 5.3 Use the project for an organisation

Organisations and users share the same URL pattern:
`https://resume.github.com/?orgname` will render an organisation résumé
using the `views/resumeOrgs.html` template instead of the user template.
The decision is made at runtime based on `data.type === 'Organization'`
in the `/users/<name>` response.

### 5.4 Customise styling on a fork

1. Fork `resume/resume.github.com` to your own GitHub account.
2. Edit `css/resume.css` and `css/print.css` — these contain all visible
   styling.
3. Push to your fork's `master` (or `main`) branch.
4. Enable **GitHub Pages** for your fork on the `master` branch root.
5. Visit `https://<your-username>.github.io/resume.github.com/?<target-user>`.

### 5.5 Embed in another site

Because the project is purely static and uses `window.location.search`
for input, embedding via `<iframe>` is straightforward:

```html
<iframe
  src="https://resume.github.com/?yourusername"
  width="800"
  height="1200"
  style="border:0">
</iframe>
```

> [!NOTE]
> The frame will inherit the parent page's print CSS only when the user
> right-clicks the iframe and chooses *Print*. A regular full-page print
> from the parent will not include the iframe content.

### 5.6 Disable Google Analytics on a fork

Open `js/githubresume.js`, find the `var trackerId = 'UA-21222559-1';`
line, and either delete it, comment it out, or set it to a falsy value.
The trailing analytics block will skip itself when `trackerId` is falsy.

## 6. Troubleshooting

Common failure modes, the cause behind each, and how to recover.

### Error: page shows the *Opt out* screen instead of a résumé

**Cause:** The user being looked up has not starred the
`resume/resume.github.com` repository on GitHub, so the
`github_user_starred_resume()` check returned `false`.

**Solution:**

1. Sign in to GitHub as the user whose résumé you want to display.
2. Visit `https://github.com/resume/resume.github.com`.
3. Click the **Star** button at the top right of the repo page.
4. Reload `https://resume.github.com/?yourusername` and wait for the
   star to propagate to GitHub's API cache (usually instant).

**Prevention:** Star the repo as the very first step. Bookmark the star
URL: `https://github.com/resume/resume.github.com`.

### Error: page shows the *API limit reached* screen

**Cause:** Your IP has hit GitHub's unauthenticated rate limit (60
requests per hour). The starred-repos check alone consumes one request
per 100 starred repos; users with many stars can blow through the budget
quickly.

**Solution:**

1. Wait until the next hour rolls over (the limit is per UTC hour, not
   sliding).
2. Switch to a different network — a mobile hotspot or VPN egress with a
   different IP often has fresh quota.
3. If you self-host, see the Admin Guide for the unsupported pattern of
   adding personal access tokens (the upstream project does not
   authenticate any of its requests).

**Prevention:** Avoid reloading the page repeatedly during testing.
Cache responses if forking.

### Error: page shows the *Not found* screen

**Cause:** The `?username` you supplied does not match any GitHub user or
organisation, so the `/users/<name>/starred` call returned HTTP 404.

**Solution:**

1. Double-check the GitHub username for typos (case-insensitive but exact
   otherwise).
2. Confirm the account still exists at `https://github.com/<username>`.
3. If the account was renamed, use the new login.

**Prevention:** Use the exact login string from the user's GitHub URL
(`https://github.com/<this-part>`).

### Error: blank page, console shows a `$ is not defined` or AJAX error

**Cause:** Either jQuery / Mustache failed to load, or you opened
`index.html` over the `file://` protocol where AJAX is blocked.

**Solution:**

1. Always serve the site over HTTP — see Section 2 for one-liner
   commands using Rack, Python, or Node.
2. Open your browser's network tab and confirm the external scripts
   (jQuery, Mustache) are resolving without 404s.
3. If on a corporate network, verify there's no proxy stripping
   third-party scripts.

**Prevention:** Use one of the supported local-dev paths in Section 2.

### Error: Google Analytics throws a CSP violation in the console

**Cause:** Your hosting environment enforces a Content Security Policy
that blocks Google Analytics scripts.

**Solution:**

1. Edit `js/githubresume.js` and remove or comment out the trailing
   `if (trackerId)` block.
2. Or extend your CSP to allow `https://www.google-analytics.com` and
   `https://ssl.google-analytics.com`.

**Prevention:** When forking, decide upfront whether you want analytics —
many forks delete the block by default.

### Error: page renders but the language list is empty

**Cause:** All of the user's non-fork repositories have a `null`
`language` field (typical for documentation-only or assets-only
repositories).

**Solution:** Nothing to do — the `#mylanguages` block is hidden via
`$('#mylanguages').hide();` when there are no languages to show.

**Prevention:** This is by design.

### Error: only forks listed, message *"All of this user's repositories seem to be forks"*

**Cause:** The user genuinely has only forked repositories. The renderer
filters out forks before sorting, so an empty `sorted` array reaches the
template.

**Solution:** None — the message is the intended display. The user must
publish original repositories on GitHub to populate the projects block.

**Prevention:** N/A — this reflects the source data accurately.

### Error: organisation count is wrong

**Cause:** The `itemCount` counter is shared between the projects block
and the organisations block. If the user already has five featured
projects, no organisations are rendered.

**Solution:** Hard-fork and split the counter into `projectCount` and
`orgCount` if you need both blocks fully populated. Edit the `run()`
function in `js/githubresume.js`.

**Prevention:** This is a known quirk of the original code path.

## 7. FAQ

**Q: Do I need to install anything to use this?**

No. Visit `https://resume.github.com/?yourusername` after starring the
`resume/resume.github.com` repo on GitHub. There is no app, no extension,
no account.

**Q: Why do I have to star the repo?**

Starring is the project's opt-in mechanism. Without a star, the front-end
shows the *opt out* screen rather than rendering your data. It also acts
as a lightweight "thanks" to the project maintainers.

**Q: Can I look up someone else's résumé?**

Yes — the URL `?username` works for any public user or organisation,
provided that user has starred the repo. If they haven't, you'll see the
opt-out screen.

**Q: Is any data sent to a server I don't control?**

The page makes calls to `api.github.com` (public REST API, JSONP) and to
Google Analytics (`google-analytics.com`). No other endpoints are
contacted. There is no first-party backend in this project.

**Q: How do I stop people from rendering my profile?**

Unstar `resume/resume.github.com` on GitHub. The next visitor will see
the opt-out screen.

**Q: Can I use my own domain?**

Yes — fork the repo, edit `css/resume.css` to your taste, and serve the
files from any static host (GitHub Pages, S3 + CloudFront, Netlify,
Vercel). See Section 2, Path B.

**Q: Does this work on mobile?**

Yes for viewing. Printing from mobile browsers is generally less reliable
than desktop — the *Save as PDF* destination is hidden behind different
menus depending on the OS. For best results, generate PDFs from a
desktop browser.

**Q: Why is my year listed as "last year" or "this year"?**

The renderer collapses year labels relative to the current year for
recently-created GitHub accounts. If you joined this calendar year, you
will see *this year*; if you joined the previous calendar year, *last
year*. Older accounts show the four-digit year.

**Q: Why does my contribution list look truncated?**

GitHub's search-issues API caps results at 1000 items. Users with very
high contribution counts will only see the first page of merged PRs. The
project does not work around this limit.

**Q: Will an organisation account work?**

Yes. The renderer checks `data.type === 'Organization'` and uses
`views/resumeOrgs.html` instead of the user template. The avatar URL is
also rewritten to a Gravatar-only form for organisation accounts.

**Q: How do I report a bug?**

Open an issue at `https://github.com/resume/resume.github.com/issues`.
Include the username you were trying to render, the browser version, and
any console output. Note that the project has been quiet for years.

**Q: Where is the source for the screen I'm seeing?**

Each visible state corresponds to one HTML file in `views/`:
`index.html` (landing), `resume.html` (user résumé), `resumeOrgs.html`
(organisation résumé), `job.html` (one project row), `org.html` (one
organisation row), `contrib.html` (one contribution row), `error.html`,
`api_limit.html`, `not_found.html`, `opt_out.html`.

**Q: Is there a CLI?**

No. The project is browser-only.

**Q: Is there a public API?**

No. The project consumes GitHub's REST API but exposes none of its own.

**Q: Are there any configuration files?**

No. Apart from `config.ru` (which is two lines of Rack to serve static
files), there are no configuration files.

---

_Generated April 2026 from resume/resume.github.com at f599075. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
