Files
pig/apps/web/index.html
T
karti de33a03524 Add the web app, seed data, and user-selectable theming
apps/web — React, Vite, Tailwind, shadcn-idiom components. Mobile Safari is a
first-class target, not an afterthought:

- Two navigation treatments rather than one compromise. A bottom tab bar on
  phones, because the top of a large phone is out of thumb reach; a persistent
  sidebar from lg upward, so an iPad in portrait gets it too.
- Safe-area insets throughout, so the tab bar clears the home indicator and the
  last row of a list is actually reachable.
- Inputs are pinned to a 16px minimum, which is the correct fix for Safari
  zooming on focus. user-scalable=no is not used: it breaks pinch-zoom for
  everyone and recent iOS ignores it anyway.
- The pipeline board becomes a stage picker on phones. An eight-column board
  scrolling horizontally on a 390px screen is technically responsive and
  practically useless.

Theming: users pick an accent and the whole interface re-tints. Accent values
live once, in @pig/core, and are written onto the root element at runtime —
there is no CSS copy to drift from the TypeScript. Preferences are stored
server-side so they follow a person between laptop and phone, mirrored into
localStorage only so the pre-paint script can avoid a white flash. Status
colours stay fixed regardless of accent: if "at risk" re-tinted to whatever
someone picked, the signal would be gone.

Seed data is public research, every record carrying a confidence grade and a
source URL. No email addresses are seeded or inferred — none are published, and
guessing them from a name and a domain is unreliable and rude. Authorship is
not promoted to employment: contributors, residency participants and alumni are
recorded as what the evidence actually shows, and a name that could not be
sourced at all is listed as unresolved rather than invented.

Three defects found and fixed by actually running it rather than assuming:

1. The seed was not idempotent. onConflictDoNothing() with no target is a no-op
   without a matching unique constraint, so a second run duplicated 27
   contacts. There is deliberately no unique index on (account, name) — two
   people at one company can share a name — so idempotency is enforced in the
   seed instead of by bending the schema.
2. /capacity scrolled sideways on a phone. Grid items default to
   min-width:auto and `truncate` sets nowrap, so a long title became
   unshrinkable content and widened the track. Fixed with min-w-0 on every
   truncating grid child.
3. The idle-capacity alert silently failed to fire at exactly 80% utilisation,
   losing a float comparison against a 0.2 threshold. Moved to 0.15, which is
   also a more sensible line for "worth attention".

The worked example is tuned to teach rather than to flatter: 70% sold at a 53%
markup lands at +6.7% margin with 20% still idle, so both the healthy number
and the alert are visible. Drop the sold share to 55% and the same block goes
underwater — that sensitivity is the argument for the product.

Verified in a real browser at 393px and 1440px, light and dark: zero horizontal
overflow on every route, zero console errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:16:43 -07:00

57 lines
2.5 KiB
HTML

<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8" />
<!--
viewport-fit=cover plus the safe-area padding in index.css is what makes
this sit correctly on notched iPhones. Without it the bottom navigation
hides under the home indicator.
Note the absence of maximum-scale / user-scalable=no: disabling zoom is a
real accessibility failure, and iOS ignores it in recent versions anyway.
The 16px minimum font size on inputs (see index.css) is the correct fix
for Safari's zoom-on-focus behaviour.
-->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="description" content="PIG — Prime Intellect Growth. An agent-native CRM for two-sided AI-compute companies." />
<!-- Matches the app chrome so Safari's toolbar blends rather than banding. -->
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#09090b" media="(prefers-color-scheme: dark)" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="pig" />
<link rel="icon" href="/pig.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/pig-touch.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>pig — Prime Intellect Growth</title>
<script>
// Applied before first paint so a dark-mode user never sees a white
// flash. Reads the last-known preference from localStorage; the
// authoritative value arrives from the server moments later and
// reconciles silently.
(function () {
try {
var mode = localStorage.getItem('pig.themeMode') || 'system';
var accent = localStorage.getItem('pig.accent') || 'pig';
var dark =
mode === 'dark' ||
(mode === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
var root = document.documentElement;
root.setAttribute('data-theme', dark ? 'dark' : 'light');
root.setAttribute('data-accent', accent);
} catch (e) {
/* Private browsing can throw on localStorage. Defaults are fine. */
}
})();
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>