--- name: 1982-build-rules description: Use when building or shipping a 1982 website or web app — one locked screen, the "..1982.." tag, Cloudflare Pages and Workers, a preview before live, and the QA checklist. --- # 1982 build rules How we build and ship 1982 sites and apps. Visual details (type, colour, motion, components) live in the style guide alongside this file: `1982-style/SKILL.md`. Follow that for how it looks; follow this for how it is put together and released. A public example of these rules in use is [pub.controller](https://pub-controller-freddys.pages.dev/). ## Look (brief) Simple Teenage Engineering plus Apple. Thin lowercase type (Inter, weight 200). Black and white, with amber (`#f2a33a`) used sparingly as a touch, not a theme. Fine grey outlines, no gradients or drop-shadow stacks. Defer the rest — tokens, type scale, cards, glyphs, motion timings — to the style guide. ## Layout: one locked screen Everything fits on one screen with no page scrolling, in portrait and landscape, unless it truly cannot (a long list or form). In that case only an inner pane scrolls. The document itself never moves. All pages are locked, apps and websites alike: pinch-zoom and bounce are off, and every page is sized to fit the device's screen. In landscape, reflow to a grid or a side-by-side layout rather than shrinking until it scrolls. Use safe-area insets on every edge so the notch and home indicator never cover content. Viewport and zoom lock (from our apps): ```html ``` Page lock and safe areas (from pub.controller / the photo-upload page): ```css :root{ --st:env(safe-area-inset-top,0px); --sb:env(safe-area-inset-bottom,0px); --made-h:16px; --made-space:calc(var(--made-h) + var(--sb)); } html,body{ height:100%; background:#000; overflow:hidden; overscroll-behavior:none; touch-action:manipulation; -webkit-text-size-adjust:100%; } body{position:fixed;inset:0} /* each screen sits above the tag */ .scr{ position:absolute;left:0;right:0;top:0;bottom:var(--made-space); padding:calc(var(--st) + 10px) calc(env(safe-area-inset-right,0px) + 22px) 14px calc(env(safe-area-inset-left,0px) + 22px); } /* a list may scroll inside; the page may not */ .list{ flex:1;min-height:0;overflow-y:auto; overscroll-behavior:contain;touch-action:pan-y; -webkit-overflow-scrolling:touch; } ``` Size the shell with `100dvh` (fall back through `svh` then `vh`) so mobile browser chrome doesn't leave a gap or force a scroll: ```css body.fit{ height:100vh;height:100svh;height:100dvh; display:flex;flex-direction:column;overflow:hidden; } ``` Inputs stay at 16px so iOS does not zoom on focus. ## The "..1982.." tag Fixed at the bottom centre of every page. Exact text: `..1982..`. Nothing else in the tag. Always visible, never overlapping content — reserve its height (`--made-space`) and sit every screen, tab bar and sheet above it (`bottom: var(--made-space)`). ```html ``` ```css .made{ position:fixed;left:0;right:0;bottom:0;height:var(--made-space); display:flex;align-items:flex-start;justify-content:center; padding-top:3px;box-sizing:border-box; pointer-events:none;z-index:70; font-weight:200;font-size:10.5px;letter-spacing:.06em; color:#5c5c5c;text-transform:lowercase;line-height:1;background:#000; } ``` ## Tone: business in the front, party in the back Keep instructions, labels and intros plain and clear. The main action comes first. Save jokes, candy, and easter eggs for one spot after a successful action — a thank-you line, not the welcome screen or a button label. Copy stays lowercase unless it is a proper name. ## Intro and add to home screen First open shows a short walkthrough: one idea per step, a short title, one or two plain lines, and a clean animated drawing in the pub.controller style (fine grey outlines on black, motion 0.6s or less, Reduce Motion shows the end frame only). Progress dots, a `next` / `let's go` button, optional `skip`, and swipe between steps. A `?` on the home screen can reopen it. Include "add to home screen" as its own step: - iPhone: Safari, Share, then "Add to Home Screen". - Android: Chrome, the ⋮ menu, then "Add to Home Screen". The icon must open straight into the app. Safari and a home-screen web app do not share storage, so if sign-in lives in the URL, that same URL must be what gets saved (keep the key in the address the person opens, and in the manifest `start_url`). A tip toast on first Safari visit is fine: `tip · share › add to home screen`. Ship it as a standalone PWA: `display: standalone`, `orientation: any`, black theme, 192 and 512 icons (plus a maskable 512). ## Stack and shipping - **Static sites** go to Cloudflare Pages. - **APIs** go to Cloudflare Workers, with D1 for data and R2 for photos and files. - **Preview first.** Deploy to a preview branch or a separate preview project. 1982 looks at that. Live (`--branch main`) only after his "go". - **Backup before every deploy.** Copy the live `dist` (and any Worker source you are changing) to a dated folder such as `backups/name-YYYYMMDD/`. Write a one-line manifest of what was live. - **Rollback ready.** Pages: dashboard → Deployments → "Rollback to this deployment", or redeploy the backup folder. Workers: `npx wrangler deployments list` then `npx wrangler rollback `. D1 data does not roll back with the code — treat destructive data changes as their own, reversible step. - **Bump the service-worker cache** on every build so phones don't keep a stale app. Hash the built HTML and write a new cache name; delete old caches on activate. Set `Cache-Control: no-cache` on `sw.js` and `index.html`; long-cache only fingerprinted fonts and images. ```js const V = 'app-' + ver; // ver = first 10 of sha1(built html) self.addEventListener('install', e => { e.waitUntil(caches.open(V).then(c => c.addAll(CORE)).then(() => self.skipWaiting())); }); self.addEventListener('activate', e => { e.waitUntil( caches.keys().then(ks => Promise.all(ks.filter(k => k !== V).map(k => caches.delete(k)))) .then(() => self.clients.claim()) ); }); ``` Typical Pages deploy, only after preview and a "go": ```bash npx wrangler pages deploy dist --project-name YOUR-PROJECT --branch main ``` - **No test data on live.** Strip preview fixtures from the live build. A `?demo=1` mode may be shipped so people can try the UI, but it must save nothing. Delete any go-live test records before staff use the app. ## Security habits - Never put secrets, PINs or keys in chat, tickets, screenshots, or public files. Store them with `npx wrangler secret put`, not in the repo. - Private links stay in a private file (mode 600), never in the app source, a group chat, or a README. - Phone numbers are managers-only. Staff devices, public pages and floor views must not receive a phone field at all — omit it from the API, don't just hide it in CSS. - Rate-limit sign-in and write endpoints on the server. Lock out after repeated failures and return 429 with `Retry-After`. Never log raw PINs or keys; store only hashes. - Private apps send `X-Robots-Tag: noindex, nofollow` and a strict referrer policy. Public pages can be indexed; they still must not contain secrets. ## Process Show 1982 the preview (with the QA screenshots below) before anything goes to staff. Nothing goes live without his own "go". If he has not said "go", it stays on the preview. ## QA checklist Before you call a build done, or ask for a "go": - [ ] iPhone screenshot in portrait - [ ] iPhone screenshot in landscape - [ ] a Pixel screenshot (portrait is enough) - [ ] no JavaScript errors in the console - [ ] the page itself does not scroll (inner lists may) - [ ] the "..1982.." tag is visible and not overlapped by buttons, titles or tab bars - [ ] no broken links - [ ] both orientations fit on one screen; notch and home bar are respected - [ ] Reduce Motion shows end frames, no movement - [ ] live has no test data Playwright sizes we actually shoot: iPhone 14 portrait and landscape, and Pixel 7. A typical iPhone frame is 390×844 at 2×. Assert the tag is clear by comparing each interactive element's box to `.made.getBoundingClientRect()`.