--- name: professional-portfolio-pwa description: Build and launch an installable, mobile-first professional portfolio PWA with verified résumé content, offline access, correct light/dark splash screens, direct recruiter actions, optional isolated Google-authenticated one-to-one chat with an owner inbox, Firebase Hosting, and a custom domain. Use when creating or publishing a personal portfolio, digital business card, or lead-generation PWA. --- # Professional Portfolio PWA Build one polished, public professional profile that opens from a link, installs on a phone, works offline, and makes it easy to contact its owner. Prefer a static PWA and Firebase Hosting for the first version; defer paid services, native app stores, AI replies, and subscription infrastructure until a working public profile has been tested. If the owner explicitly requests private recruiter chat, add Firebase Auth and Firestore with strict per-user security rules; do not ship open Firestore rules or place service-account credentials in browser code. ## Successful default design - Mobile-first editorial profile with the owner's real portrait, concise headline, location, prominent contact action, and résumé download above the fold. - Add measured accomplishments, chronological experience, scannable skills, and education below the hero. - Keep the contact options explicit: `mailto:`, `sms:`, `tel:`, and the owner's public professional profile. - Let CSS follow `prefers-color-scheme`; avoid a redundant in-app theme toggle if the owner wants the system appearance. - Use the same unaltered portrait on the page and app icon. Do not add a badge, arbitrary avatar, decorative halo, or invented photo. - Include an honest privacy page and an accurate “Get your own app” early-access CTA. Do not imply that a paid plan, checkout, live chat, or chatbot exists until it is actually implemented. ## Workflow ### 1. Verify and model the profile 1. Use a résumé explicitly provided by the owner as the source of truth. Verify names, dates, employers, role titles, education, contact details, and metrics. 2. Treat LinkedIn data as public only when it is accessible without authentication. Never request account credentials or use private LinkedIn data. Say when only a public excerpt is available; do not invent missing employment history. 3. Reuse the owner's portrait only with explicit permission. Record its source and edits. Preserve the original résumé PDF as a read-only asset. 4. Put factual profile content into a small structured JSON file. Keep private deployment configuration, tokens, API keys, and personal credentials out of both public assets and source control. 5. Verify the bundled and hosted PDF byte-for-byte with SHA-256. ### 2. Build the static PWA first Use semantic HTML, responsive CSS, and small dependency-free JavaScript. A static portfolio does not need Firebase Authentication, Firestore, Cloud Functions, server-side AI, or an npm build pipeline. Keep the profile static unless the owner explicitly requests authenticated chat or another feature that needs a backend. Include: - `index.html`, `styles.css`, and a local profile JSON file; - a Web App Manifest with portrait icons in the required sizes; - a service worker with an explicit, versioned offline app-shell cache; - the unchanged résumé and portrait; - a privacy page explaining direct handoffs and offline storage; - a Firebase Hosting configuration with restrictive security headers. Use hosting response headers to set `Cache-Control: no-cache` for mutable, unversioned app-shell resources: `/`, HTML, CSS, JavaScript, profile JSON, manifests, and `/service-worker.js`. Add explicit rules for root paths, manifests, and the service worker where the hosting provider's glob rules need them. Inspect the actual deployed response headers; do not assume the config overrides the host's default `max-age`. `no-cache` means a browser may keep a response but must revalidate it; it does not mean `no-store`. Prefer it over `no-store` for an offline PWA. Large, unchanged media may use normal or long-lived caching. Version the service-worker app-shell cache and bump its name whenever cached shell resources change. When installing a new shell, pass `Request(url, { cache: "reload" })` for mutable entries to `cache.addAll()`; otherwise a still-fresh HTTP-cache response can seed the new offline cache with stale files. Activate the new worker, remove only older caches with the app's own prefix, and claim clients. `Cache-Control` alone does not bypass a resource already served from the service worker's Cache Storage. The first version should send email/text/call actions directly to the visitor's chosen app; it should not claim to collect messages. Real in-app chat, AI replies, and an owner inbox require a separate authenticated backend and clear privacy, retention, rate-limit, and human-handoff behavior. ### 3. Respect install-prompt rules Browsers do not allow an app to force the native install confirmation on page load. Prefer a small, non-modal invitation in normal document flow at the very top of the profile, before navigation. Never obscure the portfolio, dim the page, disable interaction, or open a full-screen prompt automatically. 1. Listen for `beforeinstallprompt`. 2. Call `preventDefault()` and retain the browser event. 3. When supported, show an accessible, dismissible banner at the top of the page with **Install app** and **Not now** buttons. The browser's native confirmation must only open after the user taps **Install app**. 4. Do not assume every browser fires `beforeinstallprompt`. On iOS Safari, Android browsers with a manual home-screen install path, or desktop Chromium browsers with a browser-menu install path, show the same non-modal banner with a **How to add** action when the native event is absent. Reveal concise iOS Share → Add to Home Screen, Android browser-menu, or desktop address-bar and browser-menu instructions inline rather than in an automatic full-screen overlay. If a native event arrives later, change the action copy without restarting the countdown. Where no supported install path exists, do not show a misleading install action. 5. Keep the banner in normal document flow so it never covers or blocks the page. Respect safe-area insets, use compact responsive layout, maintain 44–48px action targets, and verify 320px, 360px, and 390px widths. Avoid unnecessary countdown timers; use one only when the owner explicitly requests a time-limited message, and respect reduced-motion preferences. 6. If the owner explicitly wants a novelty invitation on every visit, show it once per page load. A **Not now** click or self-destruct closes only the current page's invitation; do not persist shown/dismissed state across reloads. 7. After `appinstalled`, persist a local installed marker. Also detect `display-mode: standalone` and `navigator.standalone`, hide install controls, and set the marker when an installed PWA starts. If a new `beforeinstallprompt` event fires, clear a stale marker. 8. Never display the invitation in an already installed standalone PWA. Test native-prompt eligibility, Android/iOS manual fallback, dismissed, self-destructed, and already-installed states separately. Assert that an install banner is a visible top-page `