[object Object]

← back to Nineoh Guide

2026 graphics: Photoshop-baked grainy ombre hero (413KB jpg) + liquid-glass (backdrop-filter) header & frosted stat chips on web; mobile ombre LinearGradient header/bg + BlurView glass cards

3b392c3905c5ea2bce56f7d751e98ea684e22662 · 2026-07-25 13:35:50 -0700 · Steve

Files touched

Diff

commit 3b392c3905c5ea2bce56f7d751e98ea684e22662
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 25 13:35:50 2026 -0700

    2026 graphics: Photoshop-baked grainy ombre hero (413KB jpg) + liquid-glass (backdrop-filter) header & frosted stat chips on web; mobile ombre LinearGradient header/bg + BlurView glass cards
---
 apps/mobile/App.tsx            |  34 +++++++++++++++++++++++-----------
 apps/mobile/package.json       |   2 ++
 apps/web/app/globals.css       |  37 ++++++++++++++++++++++++++++++++-----
 apps/web/app/page.tsx          |  20 ++++++++++----------
 apps/web/next.config.mjs       |   3 +++
 apps/web/public/hero-ombre.jpg | Bin 0 -> 413290 bytes
 pnpm-lock.yaml                 |  32 ++++++++++++++++++++++++++++++++
 7 files changed, 102 insertions(+), 26 deletions(-)

diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 54b134d..a558723 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -10,6 +10,8 @@ import {
 } from "react-native";
 import { StatusBar } from "expo-status-bar";
 import Constants from "expo-constants";
+import { LinearGradient } from "expo-linear-gradient";
+import { BlurView } from "expo-blur";
 import AsyncStorage from "@react-native-async-storage/async-storage";
 import { APP, EpisodeSchema, type Episode } from "@nineoh/core";
 
@@ -128,15 +130,24 @@ export default function App() {
 
   return (
     <View style={styles.root}>
-      <StatusBar style="auto" />
-      <View style={styles.header}>
+      <LinearGradient
+        colors={["#f4e9f2", "#faf1ea", "#fdf3e0"]}
+        style={StyleSheet.absoluteFill}
+      />
+      <StatusBar style="light" />
+      <LinearGradient
+        colors={["#2b1e3a", "#7b3f6e", "#d9668a"]}
+        start={{ x: 0, y: 0 }}
+        end={{ x: 1, y: 1 }}
+        style={styles.header}
+      >
         <Pressable onLongPress={titleFact} delayLongPress={450}>
           <Text style={styles.title}>{APP.displayName}</Text>
         </Pressable>
         <Pressable onPress={tapBadge} hitSlop={8}>
           <Text style={styles.badge}>UNOFFICIAL</Text>
         </Pressable>
-      </View>
+      </LinearGradient>
 
       {egg ? (
         <Pressable style={styles.egg} onPress={() => setEgg(false)}>
@@ -187,7 +198,7 @@ export default function App() {
           shown.map((ep) => {
             const isSaved = saved.has(ep.id);
             return (
-              <View key={ep.id} style={styles.card}>
+              <BlurView key={ep.id} intensity={28} tint="light" style={styles.card}>
                 <View style={styles.cardRow}>
                   <Text style={styles.cardTitle}>
                     S{ep.seasonNumber}E{ep.episodeNumber} · {ep.title}
@@ -219,7 +230,7 @@ export default function App() {
                 >
                   <Text style={styles.markWatched}>✓ watched to here</Text>
                 </Pressable>
-              </View>
+              </BlurView>
             );
           })
         )}
@@ -229,7 +240,7 @@ export default function App() {
 }
 
 const styles = StyleSheet.create({
-  root: { flex: 1, backgroundColor: "#fafafa" },
+  root: { flex: 1, backgroundColor: "transparent" },
   header: {
     paddingTop: 64,
     paddingHorizontal: 20,
@@ -240,8 +251,8 @@ const styles = StyleSheet.create({
     alignItems: "baseline",
     gap: 10,
   },
-  title: { fontSize: 20, fontWeight: "700" },
-  badge: { fontSize: 11, fontWeight: "700", color: "#8a1c1c" },
+  title: { fontSize: 20, fontWeight: "700", color: "#fff8f0" },
+  badge: { fontSize: 11, fontWeight: "700", color: "#ffd27a" },
   toolbar: {
     paddingHorizontal: 20,
     paddingVertical: 8,
@@ -262,12 +273,13 @@ const styles = StyleSheet.create({
   disclaimer: { fontSize: 12, color: "#666", marginBottom: 16 },
   empty: { fontSize: 14, color: "#8a1c1c", lineHeight: 20 },
   card: {
-    backgroundColor: "#fff",
+    backgroundColor: "rgba(255,255,255,0.5)",
     borderWidth: 1,
-    borderColor: "#e5e5e5",
-    borderRadius: 8,
+    borderColor: "rgba(255,255,255,0.65)",
+    borderRadius: 14,
     padding: 14,
     marginBottom: 10,
+    overflow: "hidden",
   },
   cardRow: { flexDirection: "row", justifyContent: "space-between", gap: 8 },
   cardTitle: { fontSize: 15, fontWeight: "600", flex: 1 },
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 49a669d..ebb76d8 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -13,7 +13,9 @@
     "@nineoh/core": "workspace:*",
     "@react-native-async-storage/async-storage": "1.23.1",
     "expo": "~52.0.0",
+    "expo-blur": "~14.0.0",
     "expo-constants": "~17.0.0",
+    "expo-linear-gradient": "~14.0.0",
     "expo-status-bar": "~2.0.0",
     "react": "18.3.1",
     "react-native": "0.76.5"
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index 1d1f235..7774e1b 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -49,9 +49,11 @@ h2 { font-size: clamp(19px, 3vw, 23px); margin: 28px 0 10px; }
 /* ── Layout chrome ─────────────────────────────────────────────────────── */
 .site-header {
   position: sticky; top: 0; z-index: 20;
-  background: rgba(250, 246, 239, 0.82);
-  backdrop-filter: saturate(1.4) blur(10px);
-  border-bottom: 1px solid var(--line);
+  background: rgba(250, 246, 239, 0.62);
+  -webkit-backdrop-filter: saturate(1.8) blur(18px);
+  backdrop-filter: saturate(1.8) blur(18px);
+  border-bottom: 1px solid rgba(255, 255, 255, 0.4);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 1px 12px rgba(23, 19, 15, 0.05);
 }
 .site-header .bar {
   max-width: var(--maxw); margin: 0 auto;
@@ -84,10 +86,31 @@ h2 { font-size: clamp(19px, 3vw, 23px); margin: 28px 0 10px; }
 }
 .hero .sky {
   position: absolute; inset: 0; z-index: -2;
-  background: linear-gradient(180deg, var(--dusk-1) 0%, var(--dusk-2) 46%, var(--dusk-3) 70%, var(--sun-1) 90%, var(--sun-2) 100%);
-  background-size: 100% 220%;
+  /* Photoshop-baked grainy ombre (dusk→blush→gold) over the animated CSS gradient fallback. */
+  background:
+    url("/hero-ombre.jpg") center/cover no-repeat,
+    linear-gradient(180deg, var(--dusk-1) 0%, var(--dusk-2) 46%, var(--dusk-3) 70%, var(--sun-1) 90%, var(--sun-2) 100%);
+  background-size: cover, 100% 220%;
   animation: skyDrift 24s ease-in-out infinite alternate;
 }
+
+/* ── Liquid glass (2026 glassmorphism) ─────────────────────────────────── */
+.glass {
+  background: rgba(255, 253, 249, 0.5);
+  -webkit-backdrop-filter: blur(18px) saturate(1.8);
+  backdrop-filter: blur(18px) saturate(1.8);
+  border: 1px solid rgba(255, 255, 255, 0.38);
+  box-shadow:
+    0 8px 32px rgba(23, 19, 15, 0.10),
+    inset 0 1px 0 rgba(255, 255, 255, 0.55);
+}
+.glass-dark {
+  background: rgba(30, 20, 40, 0.34);
+  -webkit-backdrop-filter: blur(16px) saturate(1.6);
+  backdrop-filter: blur(16px) saturate(1.6);
+  border: 1px solid rgba(255, 255, 255, 0.18);
+  color: #fff6ec;
+}
 .hero .art { position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%; }
 .hero .eyebrow {
   font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
@@ -103,6 +126,10 @@ h2 { font-size: clamp(19px, 3vw, 23px); margin: 28px 0 10px; }
   transition: transform 0.15s ease, box-shadow 0.15s ease;
 }
 .hero .cta:hover { transform: translateY(-1px); text-decoration: none; box-shadow: 0 10px 26px rgba(0,0,0,0.28); }
+.hero-stats { display: flex; gap: 12px; margin-top: 26px; flex-wrap: wrap; }
+.hero-stat { border-radius: 14px; padding: 12px 20px; min-width: 92px; }
+.hero-stat-num { font-family: var(--font-display, serif); font-size: 28px; font-weight: 700; color: #fff3d6; line-height: 1; }
+.hero-stat-lab { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255,246,236,0.82); margin-top: 4px; }
 
 /* ── Cards / stats ─────────────────────────────────────────────────────── */
 .stat-row { display: flex; flex-wrap: wrap; gap: 14px; margin: 22px 0 6px; }
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index e74a843..5712228 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -67,13 +67,13 @@ export default async function Home() {
         <a className="cta rise-3" href="/episodes">
           Browse the episode guide →
         </a>
-      </section>
 
-      <div className="stat-row">
-        <Stat num={episodes} lab="Episodes indexed" />
-        <Stat num={recapped} lab="Original recaps" />
-        <Stat num={cast} lab="Cast profiles" />
-      </div>
+        <div className="hero-stats rise-3">
+          <GlassStat num={episodes} lab="Episodes" />
+          <GlassStat num={recapped} lab="Recaps" />
+          <GlassStat num={cast} lab="Cast" />
+        </div>
+      </section>
 
       <h2>Start exploring</h2>
       <div style={{ display: "grid", gap: 12, gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))" }}>
@@ -138,11 +138,11 @@ export default async function Home() {
   );
 }
 
-function Stat({ num, lab }: { num: number; lab: string }) {
+function GlassStat({ num, lab }: { num: number; lab: string }) {
   return (
-    <div className="stat">
-      <div className="num">{num}</div>
-      <div className="lab">{lab}</div>
+    <div className="glass-dark hero-stat">
+      <div className="hero-stat-num">{num}</div>
+      <div className="hero-stat-lab">{lab}</div>
     </div>
   );
 }
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index efb324c..93d781c 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -1,6 +1,9 @@
 /** @type {import('next').NextConfig} */
 const nextConfig = {
   reactStrictMode: true,
+  // Bundle everything into a self-contained server for pm2 deploy on Kamatera.
+  output: "standalone",
+  outputFileTracingRoot: new URL("../../", import.meta.url).pathname,
   // packages/core is TS source consumed directly — let Next transpile it.
   transpilePackages: ["@nineoh/core"],
   // pg is a server-only dependency; keep it out of the client bundle.
diff --git a/apps/web/public/hero-ombre.jpg b/apps/web/public/hero-ombre.jpg
new file mode 100644
index 0000000..caea1ae
Binary files /dev/null and b/apps/web/public/hero-ombre.jpg differ
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8276148..930db21 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,9 +27,15 @@ importers:
       expo:
         specifier: ~52.0.0
         version: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)
+      expo-blur:
+        specifier: ~14.0.0
+        version: 14.0.3(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)
       expo-constants:
         specifier: ~17.0.0
         version: 17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1)
+      expo-linear-gradient:
+        specifier: ~14.0.0
+        version: 14.0.2(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)
       expo-status-bar:
         specifier: ~2.0.0
         version: 2.0.1(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)
@@ -1998,6 +2004,13 @@ packages:
       react: '*'
       react-native: '*'
 
+  expo-blur@14.0.3:
+    resolution: {integrity: sha512-BL3xnqBJbYm3Hg9t/HjNjdeY7N/q8eK5tsLYxswWG1yElISWZmMvrXYekl7XaVCPfyFyz8vQeaxd7q74ZY3Wrw==}
+    peerDependencies:
+      expo: '*'
+      react: '*'
+      react-native: '*'
+
   expo-constants@17.0.8:
     resolution: {integrity: sha512-XfWRyQAf1yUNgWZ1TnE8pFBMqGmFP5Gb+SFSgszxDdOoheB/NI5D4p7q86kI2fvGyfTrxAe+D+74nZkfsGvUlg==}
     peerDependencies:
@@ -2022,6 +2035,13 @@ packages:
       expo: '*'
       react: '*'
 
+  expo-linear-gradient@14.0.2:
+    resolution: {integrity: sha512-nvac1sPUfFFJ4mY25UkvubpUV/olrBH+uQw5k+beqSvQaVQiUfFtYzfRr+6HhYBNb4AEsOtpsCRkpDww3M2iGQ==}
+    peerDependencies:
+      expo: '*'
+      react: '*'
+      react-native: '*'
+
   expo-modules-autolinking@2.0.8:
     resolution: {integrity: sha512-DezgnEYFQYic8hKGhkbztBA3QUmSftjaNDIKNAtS2iGJmzCcNIkatjN2slFDSWjSTNo8gOvPQyMKfyHWFvLpOQ==}
     hasBin: true
@@ -6210,6 +6230,12 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  expo-blur@14.0.3(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1):
+    dependencies:
+      expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)
+      react: 18.3.1
+      react-native: 0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)
+
   expo-constants@17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1):
     dependencies:
       '@expo/config': 10.0.11(supports-color@8.1.1)
@@ -6236,6 +6262,12 @@ snapshots:
       expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)
       react: 18.3.1
 
+  expo-linear-gradient@14.0.2(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1):
+    dependencies:
+      expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)
+      react: 18.3.1
+      react-native: 0.76.5(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)
+
   expo-modules-autolinking@2.0.8:
     dependencies:
       '@expo/spawn-async': 1.8.0

← 7a343d7 episode detail: prev/next adjacent-episode nav (row-comparis  ·  back to Nineoh Guide  ·  gitignore CTA test artifacts (cross-browser test passed: liq 396861c →