Real head: link previews, brand icons, and the font that never loaded

This commit is contained in:
gator 2026-08-27 16:20:14 +00:00
parent c5b05cfdb0
commit e40f53e095
16 changed files with 1292 additions and 7 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
badge-96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
favicon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

BIN
favicon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

5
favicon.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none">
<title>Pull Up — favicon</title>
<desc>Pull Up — generated by design/brand/build.mjs. Do not edit; edit the generator. Small cut: this file is only ever seen at tab size.</desc>
<rect width="32" height="32" rx="0" ry="0" fill="#0C0C0E"/><g transform="translate(1.389 1.778) scale(1.218)"><path d="M2.6 19.6c3.6.6 7-.6 9.6-3.4 2.6-2.8 5.2-4.2 8.6-3.8" fill="none" stroke="#EDEDEF" stroke-width="2.6" stroke-linecap="round"/><circle cx="18.4" cy="7.4" r="3.75" fill="#8FE388"/></g>
</svg>

After

Width:  |  Height:  |  Size: 593 B

BIN
icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icon-maskable-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
icon-maskable-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

@ -1,10 +1,110 @@
<!DOCTYPE html> <!doctype html>
<!--
The document shell. THIS is the file that ships — `app/app/+html.tsx` is the
obvious place to put a meta tag and under `web.output: "single"` it is never
evaluated. All the reasoning lives in `+html.tsx`, which is not served to
anybody; keep the two in sync and keep this comment short, because every byte
of it is downloaded by every visitor.
Three things must not change. Keep `<div id="root">`. Keep the two
substitution tokens below exactly where they are. And do not quote a
substitution token, or a closing head or body tag, anywhere in this comment:
the CLI fills the template with first-match `String.replace` calls, so a
comment that names one swallows it — which the first draft of this file did,
putting the literal title token in the tab and the bundle script in the middle
of this paragraph.
-->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Pull Up</title>
<title>Pull Up — Pickup sports at UF</title>
<meta
name="description"
content="Post a pickup game at UF, or ask to join one. Fifteen sports on the courts and fields you already walk past. Free, nothing to install, and the host approves everyone who plays."
/>
<link rel="canonical" href="https://pullup.onetouch.studio/" />
<meta name="robots" content="index, follow, max-image-preview:large" />
<!-- Open Graph. iMessage, WhatsApp, Slack, Discord, Instagram DMs and
Facebook all read these; Twitter falls back to them for anything its
own namespace does not define. Absolute URLs are not optional here —
a relative og:image is dropped by most of them. -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Pull Up" />
<meta property="og:title" content="Pull Up — Pickup sports at UF" />
<meta
property="og:description"
content="Post a pickup game at UF, or ask to join one. Fifteen sports on the courts and fields you already walk past. Free, nothing to install, and the host approves everyone who plays."
/>
<meta property="og:url" content="https://pullup.onetouch.studio/" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://pullup.onetouch.studio/og.png" />
<meta property="og:image:type" content="image/png" />
<!-- Declared dimensions let a client lay out the card before the image has
downloaded, which is the difference between a preview that appears with
the message and one that pops in a second later. -->
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta
property="og:image:alt"
content="The Pull Up mark on a near-black field, above the line: There is a game on tonight."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Pull Up — Pickup sports at UF" />
<meta
name="twitter:description"
content="Post a pickup game at UF, or ask to join one. Fifteen sports on the courts and fields you already walk past. Free, nothing to install, and the host approves everyone."
/>
<meta name="twitter:image" content="https://pullup.onetouch.studio/og.png" />
<meta
name="twitter:image:alt"
content="The Pull Up mark on a near-black field, above the line: There is a game on tonight."
/>
<!-- Icons. Every one of these is a fixed path in `public/`, because Metro
content-hashes anything it bundles and a manifest cannot point at a
name that changes each build. Placed by design/web-assets.mjs from the
cuts in design/brand/. The .ico goes first with `sizes="any"`: that is
the ordering that makes a browser which understands SVG take the SVG
and one which does not fall back cleanly. -->
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0C0C0E" />
<meta name="color-scheme" content="dark" />
<!-- Add-to-home-screen. This is not decoration: iOS will not deliver a web
push at all unless the site has been added to the home screen, so the
standalone metadata is load-bearing for notifications, not just for
looks. `black` rather than `black-translucent` because the viewport is
not `viewport-fit=cover` — translucent without cover leaves a white
band above a near-black app. -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Pull Up" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="application-name" content="Pull Up" />
<!-- Archivo, the app's face. `lib/theme.ts` asks every screen for it and
until this line shipped nothing ever loaded it, so the whole product
rendered in the system UI font. It belongs in the document rather than
in the JS bundle: a font requested by a chunk that has not parsed yet
starts downloading a second too late. -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@75..125,400..900&display=swap"
rel="stylesheet"
/>
<!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element --> <!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element -->
<style id="expo-reset"> <style id="expo-reset">
/* These styles make the body full-height */ /* These styles make the body full-height */
@ -23,7 +123,53 @@
flex: 1; flex: 1;
} }
</style> </style>
<link rel="icon" href="/favicon.ico"/></head>
<style id="pullup-base">
html, body { background: #0C0C0E; color-scheme: dark; }
body { font-family: Archivo, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
/* One soft light source behind everything so large dark areas have
depth instead of reading as a flat void. Fixed, so it does not
travel with the scroll. */
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
/* Barely there. Enough that a large dark area is not flat,
not enough to read as a coloured glow. */
background:
radial-gradient(60rem 40rem at 82% -10%, rgba(143,227,136,0.045), transparent 60%),
radial-gradient(50rem 36rem at 4% 106%, rgba(255,255,255,0.02), transparent 58%);
z-index: 0;
}
#root { position: relative; z-index: 1; }
::selection { background: rgba(143,227,136,0.25); }
/* The default scrollbar on a dark page is a bright grey slab. */
* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.16) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.14);
border-radius: 99px;
border: 3px solid transparent;
background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.24); background-clip: content-box; }
*::-webkit-scrollbar-track { background: transparent; }
input, textarea { font-family: inherit; }
/* Autofill repaints Chrome inputs white, which looks broken here. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-text-fill-color: #EDEDEF;
-webkit-box-shadow: 0 0 0 1000px #141417 inset;
transition: background-color 9999s ease-in-out 0s;
}
</style>
</head>
<body> <body>
<!-- Use static rendering with Expo Router to support running without JavaScript. --> <!-- Use static rendering with Expo Router to support running without JavaScript. -->
@ -32,6 +178,6 @@
</noscript> </noscript>
<!-- The root element for your Expo app. --> <!-- The root element for your Expo app. -->
<div id="root"></div> <div id="root"></div>
<script src="/_expo/static/js/web/entry-87229ee88c358aff8c4dc06b2c66745f.js" defer></script> <script src="/_expo/static/js/web/entry-0378b3b321364495a1241c81b00660ae.js" defer></script>
</body> </body>
</html> </html>

BIN
og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

21
site.webmanifest Normal file
View file

@ -0,0 +1,21 @@
{
"id": "/",
"name": "Pull Up — Pickup sports at UF",
"short_name": "Pull Up",
"description": "Post a pickup game at UF, or ask to join one. Fifteen sports on the courts and fields you already walk past. Free, nothing to install, and the host approves everyone who plays.",
"lang": "en-US",
"dir": "ltr",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#0C0C0E",
"theme_color": "#0C0C0E",
"categories": ["sports", "social"],
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" },
{ "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
{ "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png", "purpose": "any" }
]
}

15
sw.js
View file

@ -68,9 +68,18 @@ async function handlePush(event) {
await self.registration.showNotification(title, { await self.registration.showNotification(title, {
body, body,
// The maskable icon already in the export. No emoji anywhere in this app. /* Both of these must be fixed paths in this same public/ folder. They used
icon: '/assets/icon.png', * to point at `/assets/icon.png`, which has never existed in an export —
badge: '/assets/icon.png', * Metro content-hashes everything it bundles, so nothing it produces has a
* stable name, and every notification this app has ever shown fell back to
* the browser's own generic glyph. Verify with a HEAD request against a
* deployed build before changing either.
*
* Android reduces `badge` to a monochrome silhouette, so it gets the
* white-on-transparent cut rather than the full icon, which would come back
* as an unreadable grey blob. No emoji anywhere in this app. */
icon: '/icon-192.png',
badge: '/badge-96.png',
/* Collapse by conversation. Five chat messages from one game should be one /* Collapse by conversation. Five chat messages from one game should be one
* line on the lock screen, not five -- `renotify` still buzzes for each so * line on the lock screen, not five -- `renotify` still buzzes for each so
* nothing is silently swallowed. A notification with no session falls back * nothing is silently swallowed. A notification with no session falls back