Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 352 additions & 10 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,363 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PI — your Princeton desk</title>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='14' fill='%2314B8CE'/%3E%3Ctext x='16' y='22' font-size='16' text-anchor='middle' fill='white' font-family='Georgia,serif'%3Eπ%3C/text%3E%3C/svg%3E"
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<title>PI, your Princeton desk</title>
<meta
name="description"
content="A Princeton chat that reads the TigerApps you switch on: course reviews, your schedule, your degree. Sign in with your NetID."
/>
<meta name="theme-color" content="#fdfcf9" />
<meta name="color-scheme" content="light" />

<meta property="og:type" content="website" />
<meta property="og:site_name" content="PI, by TigerApps" />
<meta property="og:title" content="PI, your Princeton desk" />
<meta
property="og:description"
content="Ask once. PI reads the rest: course reviews, your schedule, what's left on your degree. It only reads the TigerApps you switch on. Princeton NetID."
/>
<meta property="og:url" content="https://pi.tigerapps.org" />
<!-- og.png still carries the pre-launch headline ("one chat for your
courses…"), so the alt below describes the image as it actually is.
Regenerate the card to lead with "Ask once. PI reads the rest.", then
update the alt with it. -->
<meta property="og:image" content="https://pi.tigerapps.org/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="PI by TigerApps: one chat for your courses, your schedule and your degree."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="PI, your Princeton desk" />
<meta
name="twitter:description"
content="Ask once. PI reads the rest: course reviews, your schedule, what's left on your degree. It only reads the TigerApps you switch on."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<meta name="twitter:image" content="https://pi.tigerapps.org/og.png" />

<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="alternate icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />

<!-- Caveat is self-hosted (latin subset, variable 400-700), so the brand
face costs one same-origin request instead of two cross-origin ones. -->
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;600;700&display=swap"
rel="stylesheet"
rel="preload"
href="/fonts/caveat-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>

<!-- Critical CSS: enough of the design system to paint the boot shell
before the bundle lands. Values are literals on purpose — this block
must not depend on anything that ships in the stylesheet. Keep in step
with src/client/styles.css. -->
<style>
:root {
--paper: #fdfcf9;
--ink: #22262e;
--ink-faint: #767063;
--rule: #e7e2d7;
--rule-soft: #f3f1ea;
--pi-deep: #0a7180;
--pi-wash: #e4f4f6;
--stock-sidebar: #faf8f1;
--hand: "Caveat", "Caveat Fallback", "Bradley Hand", "Segoe Print",
cursive;
--sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
system-ui, sans-serif;
color-scheme: light;
}
@font-face {
font-family: "Caveat";
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url(/fonts/caveat-latin.woff2) format("woff2");
}
@font-face {
font-family: "Caveat Fallback";
src: local("Bradley Hand"), local("Bradley Hand Bold"),
local("Segoe Print"), local("Chalkboard SE"), local("Apple Chancery");
size-adjust: 73%;
ascent-override: 131%;
descent-override: 41%;
line-gap-override: 0%;
}
* {
box-sizing: border-box;
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font-family: var(--sans);
font-size: 15px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.shell {
display: flex;
height: 100%;
height: 100dvh;
}
.sidebar {
width: 264px;
flex-shrink: 0;
border-right: 1px solid var(--rule);
background: var(--stock-sidebar);
display: flex;
flex-direction: column;
padding: 16px 12px 12px;
gap: 4px;
}
.main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.pi-lozenge {
display: inline-grid;
place-items: center;
width: 26px;
height: 26px;
border-radius: 999px;
background: var(--pi-wash);
color: var(--pi-deep);
flex-shrink: 0;
}
.pi-lozenge-lg {
width: 52px;
height: 52px;
}
.skel {
position: relative;
overflow: hidden;
background: var(--rule-soft);
border-radius: 6px;
}
.skel::after {
content: "";
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.8),
transparent
);
animation: skel-sweep 1.5s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.skel-line {
height: 10px;
border-radius: 999px;
}
@keyframes skel-sweep {
to {
transform: translateX(100%);
}
}
.boot-shell {
pointer-events: none;
}
.boot-sidebar {
gap: 8px;
}
.boot-wordmark {
display: flex;
align-items: center;
gap: 12px;
padding: 2px 8px 16px;
}
.boot-bones {
display: flex;
flex-direction: column;
gap: 4px;
}
.boot-bones .skel-line:first-child {
width: 34px;
}
.boot-bones .skel-line:last-child {
width: 84px;
height: 8px;
}
.boot-newchat {
height: 36px;
border-radius: 999px;
margin: 0 4px 12px;
}
.boot-rows {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
.boot-rows.divided {
margin-top: 16px;
padding-top: 16px;
border-top: 1px dashed var(--rule);
}
.boot-rows .skel-line:nth-child(1) {
width: 62%;
}
.boot-rows .skel-line:nth-child(2) {
width: 78%;
}
.boot-rows .skel-line:nth-child(3) {
width: 68%;
}
.boot-rows .skel-line:nth-child(4) {
width: 84%;
}
.boot-rows .skel-line:nth-child(5) {
width: 71%;
}
.boot-main {
align-items: center;
justify-content: center;
}
.boot-center {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
margin-top: -8vh;
}
.boot-note {
font-family: var(--hand);
font-size: 20px;
color: var(--ink-faint);
margin: 0;
}
.boot-noscript {
max-width: 30rem;
margin: 12vh auto 0;
padding: 0 24px;
text-align: center;
}
@media (max-width: 820px) {
.sidebar {
position: fixed;
inset: 0 auto 0 0;
transform: translateX(-100%);
}
}
@media (prefers-reduced-motion: reduce) {
.skel::after {
display: none;
}
}
</style>
</head>
<body>
<div id="root"></div>
<!--
Boot shell. React's first render replaces all of this, so it exists only
to make the first paint PI's own furniture instead of a white page.

App.tsx's auth-loading state must render THIS EXACT TREE so nothing jumps
when React takes over. The contract, top to bottom:

div.shell.boot-shell[aria-busy]
div.sidebar.boot-sidebar[aria-hidden]
div.boot-wordmark
span.pi-lozenge > PiMark (Icons.tsx), size 16
span.boot-bones > 2 x span.skel.skel-line
div.skel.boot-newchat
div.boot-rows > 4 x span.skel.skel-line (nav)
div.boot-rows.divided > 5 x span.skel.skel-line (chats)
div.main.boot-main
div.boot-center
span.pi-lozenge.pi-lozenge-lg > PiMark, size 30
p.boot-note > "opening your desk…"

Row widths come from :nth-child rules, so the markup carries no inline
styles. Everything above is also defined in src/client/styles.css.
-->
<div id="root">
<div class="shell boot-shell" aria-busy="true" aria-label="Opening PI">
<div class="sidebar boot-sidebar" aria-hidden="true">
<div class="boot-wordmark">
<span class="pi-lozenge">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M4.6 7.4h14.8" />
<path d="M9.4 7.4v8c0 1.9-.9 3-2.7 3.4" />
<path d="M15.2 7.4v8.3c0 1.6.9 2.5 2.6 2.7" />
</svg>
</span>
<span class="boot-bones">
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
</span>
</div>
<div class="skel boot-newchat"></div>
<div class="boot-rows">
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
</div>
<div class="boot-rows divided">
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
<span class="skel skel-line"></span>
</div>
</div>
<div class="main boot-main">
<div class="boot-center">
<span class="pi-lozenge pi-lozenge-lg">
<svg
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M4.6 7.4h14.8" />
<path d="M9.4 7.4v8c0 1.9-.9 3-2.7 3.4" />
<path d="M15.2 7.4v8.3c0 1.6.9 2.5 2.6 2.7" />
</svg>
</span>
<p class="boot-note">opening your desk…</p>
</div>
</div>
</div>
</div>
<noscript>
<p class="boot-noscript">
PI needs JavaScript to run. Turn it on for this site and reload.
</p>
</noscript>
<script type="module" src="/src/client/main.tsx"></script>
</body>
</html>
Loading
Loading