Skip to content
erikkunzPublic

About

Portpass — Travel eligibility, mapped around the documents you actually hold.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

90 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

portpass.world liquid-glass icon

portpass.world

Travel eligibility, mapped around the documents you actually hold.

Portpass checks passports, residence permits and visas together, then turns the result into a private, browser-local travel and residence map.

Open portpass.world · Features · Appearance & sharing · Sources · Deployment

No accounts · no uploaded document scans · profiles stay local unless you export them yourself

Portpass world map and local travel wallet interface

Features

  • Multiple passports, residence permits and visitor visas, linked to a passport, with optional expiry dates.
  • Interactive pan/zoom world map, searchable country list and Visit/Live modes.
  • Show all destinations matching the current search and filter, or load more in batches.
  • System, Light and Dark appearance modes plus six accent palettes. Rose is the default; Green, Amber, Violet, Teal and Slate are available from the header menu.
  • Share map previews a JPEG with your travel wallet, current Visit/Live counters, a grid-free world map, and Portpass branding. The first image follows the active site theme, while export-only theme controls can regenerate it without changing the website theme. Download it or use native file sharing where supported. Images are generated locally in the browser.
  • Best available route plus all assessed alternatives, conditions and provenance.
  • Browser-local wallet. No scans or document numbers; no third-party runtime requests.
  • Empty first-run state. Add a passport to begin. Expired documents and documents without an active linked passport are excluded.
  • Hostname-aware Stable and Alpha environments. The same code can safely render different environment labels, footer destinations and indexing behaviour on portpass.world and alpha.portpass.world.
Portpass app screenshot

Appearance and sharing

Site themes

theme.js owns the browser-local appearance preferences. The Light/Dark selector stores portpass-theme; the accent picker stores portpass-palette. Both are local device preferences and are not included in exported .portpass profiles.

The available accent palettes are Rose (default), Green, Amber, Violet, Teal and Slate. The palette controls the main surfaces, map background, highlighted cards, controls, dialogs and generated guide styling. Keyboard focus rings also use the active accent token rather than a fixed colour, so focus styling follows the currently selected palette in both light and dark modes.

System remains the default colour-mode preference. The selected appearance is applied to the main app, legal/error pages and generated guide pages. Generated guide links keep the surrounding text colour and use an underline as the link cue rather than falling back to browser-default blue/purple colours.

A one-time New: Custom Themes! nudge is eligible to appear on the second distinct homepage session. Session storage prevents a reload from counting as another visit; local storage records the visit count and whether the nudge has already appeared. It disappears after 30 seconds, on dismissal, or when the menu is opened. Clicking the nudge itself opens the menu. This tracking is browser-local and contains no wallet data.

Map image sharing

map-export.js draws the exported JPEG directly from the map/rules data on a local canvas. It does not use a screenshot service, DOM capture service or remote image renderer.

The initial JPEG inherits the website's currently resolved Light/Dark mode and accent palette. After it is generated, the share dialog exposes a separate Image theme control with Light/Dark and all six accent colours. These controls are export-only: they regenerate the current JPEG automatically, update the download/share file, and do not write portpass-theme, portpass-palette or the document's active theme attributes.

The share dialog keeps Download JPEG and native Share actions beside the close control and displays the generated JPEG inside a bounded image frame. The preview is constrained to the available viewport so its intrinsic dimensions cannot enlarge the modal or overflow the frame. On smaller screens the controls stack as needed.

All export state is temporary browser state. The generated JPEG may contain the user's wallet labels and current Visit/Live results, so anything visible in the image is visible to whoever receives the file. Nothing in the JPEG generation flow is uploaded to Portpass.

Sources and limitations

data/passports.json: imorte/passport-index-data, MIT, upstream last updated 2026-02-17, retrieved 2026-09-12. 199 passport origins. The original ilyankou dataset is archived. Upstream data is derived from Passport Index; it is a community dataset, not an official or live admission check. License notice is retained beside the data. No individual corridor freshness or official-source audit is implied.

rules.js: independent rules reviewed 2026-09-12, covering:

  • UK–Ireland Common Travel Area visits and residence for British and Irish citizens, in both directions.
  • EU/EEA free movement, the EU–Switzerland agreement, and EFTA mobility. Residence conditions and registration remain applicable. Liechtenstein requires quota approval and is shown as Check eligibility, not established residence rights.
  • EU/Schengen ordinary-passport short-stay waivers, including biometric, Taiwanese national-ID-number and Hong Kong/Macao SAR-passport conditions. The wallet asks for yes/no confirmation, never the ID number. Missing confirmation is Check eligibility; a negative answer does not qualify for the waiver. These rules apply to Schengen destinations, not Ireland or Cyprus. No reciprocal entry rights outside Schengen are inferred.
  • Vanuatu’s removed waiver and Nauru’s not-yet-applicable waiver; the EU–Brazil stay-calculation exception is identified rather than reduced to a generic day count.
  • TTTA: Australian citizens → New Zealand resident visa on arrival; New Zealand citizens → Australian subclass 444 visa on arrival, with residence/work/study conditions. Australian permanent residents get a conditional New Zealand route requiring return travel conditions and normally an NZeTA.
  • US green cards support Canadian and Mexican short-visit exemptions. Mark a US residence permit as permanent residence in the wallet; an unspecified permit is conditional and a temporary permit does not qualify.
  • Qualifying third-country visas and residence permits support visits to Albania, Serbia and Montenegro. Albania’s visa route checks multiple entry and previous use. These are destination-specific exemptions, not blanket access.
  • Greenland is an additional destination, separate from Denmark/Schengen, with passport waivers, eligible Schengen residence permits, explicitly Greenland-valid visas and Nordic residence rights. Choose Denmark for a Danish passport, including one issued in Greenland.
  • UK/Canadian ordinary passports → mainland China: sourced 30-day waiver for entry 17 February–31 December 2026. After the published end date, show an eligibility check pending re-verification.
  • Schengen short stays with an eligible residence permit or uniform type C visa. These documents alone do not grant residence in other countries.

BOTC coverage includes Gibraltar, the Falkland Islands, Bermuda and the Cayman Islands. In the passport/citizenship selector, choose the territory marked BOTC only for that nationality class; choose United Kingdom · British citizen for British-citizen nationality even if the passport was issued overseas. The wallet asks separately about documented local right of abode or unrestricted residence status. A BOTC passport never inherits the UK passport matrix, CTA residence rights or the UK–China waiver.

Outbound BOTC rules cover UK visits (including the ETA exemption), Schengen short visits, Canada’s air eTA route and Greenland’s Danish-nationality visa exemption. Inbound coverage includes the Falklands’ own visa list, Gibraltar’s July 2026 arrangements and British-citizen visitor access to Bermuda/Cayman. Living requires confirmed local status or a held residence permit; otherwise sourced application requirements are shown as Check eligibility. Other combinations remain unassessed. Small territories without Natural Earth polygons remain searchable.

Turkish association rights: edit an EU residence-permit entry and choose a qualifying worker (1/3/4-year), family (3/5-year) or vocationally trained child stage, then confirm its conditions. Confirmed stages appear as treaty residence rights only in that host country. Missing confirmation stays conditional; nationality alone adds no automatic residence rights. Family beneficiaries may hold another nationality. Dutch self-employed applications and UK legacy ECAA worker/business/dependant extensions remain subject to approval. No association tourist-visa waiver is inferred.

Source links, decision boundaries, and maintenance notes: agreement coverage. Country details link to the applicable authority and review date.

The passport option means an ordinary citizen passport (GB means British citizen). Diplomatic passports, other British nationality classes and special travel documents are not assessed. Legacy wallet entries with missing passport-condition fields remain unconfirmed. All confirmation fields are stored locally with the wallet.

Other visas/permits record declared possession for their issuing destination, subject to their conditions. A UK or Irish residence permit does not grant CTA citizenship rights; an EU or Swiss permit does not confer citizenship-based free movement. General work eligibility is not assessed beyond the stated treaty conditions. No-admission records are not overridden by a visa or permit linked to that passport, or by a short-stay visa waiver. An alternative passport may have its own route. Unassessed residence routes are not denials; visa-free tourism never implies residence rights.

Not yet covered: most third-country document exemptions, other family-member rights, UK Withdrawal Agreement status, EU long-term resident and Blue Card mobility, unassessed Turkish association exceptions, other bilateral residence schemes, long-stay visas, travel history, remaining entries, individual restrictions and travel-date authorisation requirements. These require additional eligibility inputs or destination-specific assessment. Visa-facilitation agreements simplify applications; they do not create visa-free travel or automatic residence. This is a dated rules snapshot, not an exhaustive or live treaty database. IATA Timatic is a production integration candidate; obtain access and licensing terms from IATA.

data/world.geojson: Natural Earth 1:110m administrative boundaries, public domain, obtained from https://github.com/nvkelso/natural-earth-vector. Small countries may not have polygons; all dataset countries remain in the list. Crimea is included in Ukraine: its polygon was removed from Russia and unioned with Ukraine by dissolving their shared edges, following Andrew Heiss’s guide. The adjustment preserves the clockwise outer-ring winding used by D3. Reapply this adjustment when updating the Natural Earth source; verify that [34, 45] belongs to Ukraine and not Russia.

vendor/d3.min.js: D3 7.9.0, ISC; license included. All runtime assets are local.

Maintenance

Review upstream timestamp and changes before replacing the passport JSON. Preserve its license. Update the displayed snapshot date in index.html and app.js. Add rule regression cases in tests/rules.test.js when extending rules.js. Keep missing information as unknown. Do not treat third-party scraped data as guaranteed current or complete.

The static 404 page uses root-relative site assets (/style.css, /theme.js, /icon.svg/favicon assets and / navigation) because Cloudflare's 404-page handling serves the error document while retaining the missing URL in the address bar. Relative assets would otherwise resolve below the nonexistent path and leave the page unstyled.

Tests: node tests/rules.test.js from this directory.

Browser checks: install Playwright in your development environment, start the server above, then run node tests/browser.cjs. Run node tests/map-export.cjs for JPEG download and sharing checks. Set PORTPASS_URL to test another URL. Screenshots are written to /tmp/portpass-desktop.png and /tmp/portpass-mobile.png.

Run npm test for the full rule, timing, profile, SEO, appearance and map-export regression suite. Test files document important boundaries such as BOTC jurisdiction handling, hostname-aware environment labels, export-only theme changes and generated SEO theming.

Cloudflare deployment

This is a static site with a deterministic SEO build step and can be deployed either as a Workers Static Assets application or as a Cloudflare Pages project.

Stable and Alpha environments

The production site is https://portpass.world/; the experimental environment is https://alpha.portpass.world/. Environment-specific UI is determined by location.hostname, not by hardcoded branch text. This allows the same implementation to be promoted from alpha to main without making the production hostname behave like Alpha.

On alpha.portpass.world, the header environment badge reads ALPHA. On other hostnames, including portpass.world, it reads BETA. The footer switch is also hostname-aware: Stable offers View alpha branch and shows a warning before navigating to Alpha; Alpha offers View stable website and shows a confirmation before returning to production. The Alpha → Stable confirmation includes a visible 10-second circular countdown and redirects automatically at zero unless cancelled. Clicking Continue redirects immediately.

Alpha indexing protection is likewise hostname-aware. The build post-processor injects a small guard into the homepage, generated guides, 404 and Impressum. Only when the runtime hostname is exactly alpha.portpass.world does it create/update <meta name="robots" content="noindex">. On portpass.world the guard exits without changing robots metadata, so the same code can exist in main without noindexing production.

Do not add a blanket Disallow for the Alpha hostname to the shared robots.txt as a substitute for this guard. Search engines need to be able to fetch the page to observe its noindex directive. The public production crawler policy and sitemap remain unchanged.

Programmatic SEO

Build the static, engine-backed mobility guides before a deployment:

npm run build:seo

This produces the controlled guide corpus in passport/ and travel/, plus sitemap.xml and robots.txt. The deploy commands run this step automatically. The generated files are intentionally ignored by Git and included in the Cloudflare asset upload through .assetsignore.

The build post-processor also applies the shared theme script/styles, favicon markup and the hostname-aware Alpha indexing guard to generated/static HTML. The liquid-glass app mark ships as a square 96×96 PNG for search engines, a self-contained SVG browser fallback, a multi-size ICO and a 180×180 Apple touch icon. Keep those root icon files in the Cloudflare asset allowlist.

The generated crawler policy allows normal search engines and AI crawlers through User-agent: *, with explicit allowances for OAI-SearchBot, GPTBot, PerplexityBot, Perplexity-User, Claude-SearchBot, Claude-User and ClaudeBot, and advertises https://portpass.world/sitemap.xml. Wallets and imported profiles stay in the browser; only public reference guides enter the sitemap. Do not publish personal wallet results as indexable pages. If server-hosted personal results are introduced, exclude them from the sitemap and serve noindex (robots.txt alone does not prevent indexing).

Neither Wrangler configuration sets bot access policies. Separately check the Cloudflare zone's AI crawler blocking, managed robots.txt and WAF/challenge rules if crawlers cannot reach public pages; these dashboard settings can override the site policy. Keep unrelated security protections enabled.

After a CLI deployment, npm run submit:indexnow sends the public sitemap URLs to the IndexNow endpoint. Ownership is verified by the public key file at the site root. The submission script rejects empty, oversized or off-domain URL sets; it never submits local wallet state or user-generated URLs. Run node scripts/submit-indexnow.js --dry-run to validate the payload without contacting IndexNow.

Install the local Cloudflare CLI and authenticate once:

npm install
npx wrangler login

Run it locally through the Workers runtime:

npm run dev

Deploy as a Worker:

npm run deploy:worker

For Git-connected Workers Builds, use npm run build:seo as the build command; the Worker deployment itself is handled by the connected build. The root .assetsignore allows only the site's files, data/, and vendor/ to be uploaded. Add new public files there when needed. Wrangler does not support assets.exclude in wrangler.jsonc; without .assetsignore, using the repository root as the asset directory also uploads dependencies such as node_modules/workerd.

Deploy to Pages (the Pages project must exist, or Wrangler will prompt to create it):

npm run deploy:pages

For a Git-connected Pages project, use npm run build:seo as the build command and . as the build output directory. The wrangler.pages.jsonc file records the same configuration for CLI deployments. The Worker and Pages configs are separate because Cloudflare uses different configuration keys for those services.

Visa clocks keep allowance, validity, arrival, admission deadline and previous visits in the existing local browser wallet. Edit any document to update its dates. Select a clock to highlight its destination or Schengen bloc; hover or open a country for the corresponding countdown. Entry and exit days count, and remaining days include today. No timing details are sent to a server.

Blank allowances are explicitly labelled as assumptions: the Schengen maximum (90 days), the usual UK Standard Visitor allowance (six calendar months), or a passport-matrix numeric visa estimate where available. Visa-free/ETA allowances are not substituted for held visas. There is no universal issued-visa default database: unsupported allowances remain unknown. US admission deadlines come from the user's I-94, separately from entry-visa expiry.

Schengen estimates combine logged short visits across all Schengen visas in the wallet, including expired visas and other linked passports. The user must confirm complete history before a remaining stay is shown. A shorter entered total allowance also applies. Old visits fall out of the rolling 180-day window; overlapping records count once. Add separate historical visas as needed. Unrecorded visits, single/multiple-entry restrictions, nationality-specific exceptions and residence-authorised periods require the user's own checks; these clocks do not verify legal entitlement.

Citizenship without a passport is a separate wallet type. It adds a residence result for that country in the Live tab only. It never substitutes for a passport, links a visa/permit, or adds passport travel or treaty routes abroad.

Advanced below the map downloads a readable, indented JSON file with a .portpass extension. The optional name determines the filename (default profile.portpass). Version 1 contains format: "portpass", version: 1, name, and documents, including visa timing and history. Import reads the file locally, validates it, and previews the entry count before the user replaces the current wallet. Invalid files leave the wallet untouched. Save the current profile first to keep it. Profiles are plain text, not encrypted; files are never uploaded to Portpass.

tests/entry-browser.js checks the new document fields, saved profiles, Greenland map/list, TTTA and China in a disposable browser profile. Run checkEntryRules() on the loaded local site. Third-country exemptions use the destination’s stay conditions, not the issuing visa’s stay clock.

BOTC regression checks: tests/botc.test.js is included in npm test; run checkBotc() from tests/botc-browser.js in a disposable browser profile for form, edit and profile checks.

tests/association.test.js covers host-country and nationality boundaries, qualifying stages, inactive documents and profile validation. checkAssociation() in tests/association-browser.js checks the residence form, confirmation resets and saved profiles.

Settlement blocs, directly above Advanced, opens a separate public membership map. Its 24 colour-key entries cover the 21 overview blocs/initiatives plus enhanced CARICOM, EU–Switzerland and EFTA. Click a key entry to isolate a group, or Show all blocs to restore the overview. Overlapping memberships use continuous diagonal stripes containing every applicable colour; small states and territories omitted from the base map have markers. Country details explain the actual rights and conditions, including bilateral and proposed arrangements. Returning to the personal map preserves the wallet, mode, search and zoom.

bloc-map.js builds this view from the supported rules, independently of wallet evaluations. checkBlocMap() in tests/bloc-map-browser.js checks all member geometries/markers, overlap colours, key filtering, keyboard interaction, desktop/mobile layout, themes and preservation of personal state in a disposable browser profile.

About

Portpass — Travel eligibility, mapped around the documents you actually hold.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages