[object Object]

← back to Nineoh Guide

App.tsx: add ShowInfo type + show info banner (chips, expandable desc, disclaimer) below show selector

5b12116c4c6c161fcd44e38841762c3c618a18a2 · 2026-08-06 04:17:18 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 5b12116c4c6c161fcd44e38841762c3c618a18a2
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Thu Aug 6 04:17:18 2026 -0700

    App.tsx: add ShowInfo type + show info banner (chips, expandable desc, disclaimer) below show selector
---
 apps/mobile/App.tsx | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 117 insertions(+), 1 deletion(-)

diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 7cba875..27c5c57 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -50,7 +50,20 @@ const SELECTED_SHOW_KEY = "nineoh.selectedShow.v1";
 // The two shows this guide covers. `canonical_title` matches the DB seed.
 const ORIGINAL_CANONICAL = "beverly-hills-90210";
 
-type Show = { id: string; canonicalTitle: string; displayTitle: string };
+type ShowInfo = {
+  description: string;
+  seasonCount: number;
+  episodeCount: number;
+  premiereDate: string | null;
+  finaleDate: string | null;
+  disclaimerText: string;
+};
+type Show = {
+  id: string;
+  canonicalTitle: string;
+  displayTitle: string;
+  showInfo?: ShowInfo;
+};
 
 // Short, friendly label for the show toggle. Derived from displayTitle but
 // pinned to the copy the brief specifies for the two known shows.
@@ -254,6 +267,7 @@ export default function App() {
   const [revealed, setRevealed] = useState<Set<string>>(new Set());
   const [eggTaps, setEggTaps] = useState(0);
   const [egg, setEgg] = useState(false);
+  const [showInfoExpanded, setShowInfoExpanded] = useState(false);
   const [appleUser, setAppleUser] = useState<{ id: string; name: string | null } | null>(
     null
   );
@@ -594,6 +608,66 @@ export default function App() {
         </View>
       ) : null}
 
+      {/* Show info banner — subtle strip below the selector showing original show details */}
+      {(() => {
+        const sel = shows.find((s) => s.id === selectedShowId);
+        const info = sel?.showInfo;
+        if (!info) return null;
+        // Split description into sentences; show first 2, rest behind "more"
+        const sentences = info.description.match(/[^.!?]+[.!?]+/g) ?? [info.description];
+        const preview = sentences.slice(0, 2).join(" ").trim();
+        const rest = sentences.slice(2).join(" ").trim();
+        const premiereYear = info.premiereDate ? info.premiereDate.slice(0, 4) : null;
+        const finaleYear = info.finaleDate ? info.finaleDate.slice(0, 4) : null;
+        const yearRange =
+          premiereYear && finaleYear ? `${premiereYear}–${finaleYear}` : premiereYear ?? null;
+        return (
+          <View style={styles.showInfoBanner}>
+            <View style={styles.showInfoChips}>
+              {info.seasonCount > 0 && (
+                <View style={styles.showInfoChip}>
+                  <Text style={styles.showInfoChipText}>
+                    📺 {info.seasonCount} Season{info.seasonCount !== 1 ? "s" : ""}
+                  </Text>
+                </View>
+              )}
+              {info.episodeCount > 0 && (
+                <View style={styles.showInfoChip}>
+                  <Text style={styles.showInfoChipText}>🎬 {info.episodeCount} Episodes</Text>
+                </View>
+              )}
+              {yearRange && (
+                <View style={styles.showInfoChip}>
+                  <Text style={styles.showInfoChipText}>📅 {yearRange}</Text>
+                </View>
+              )}
+            </View>
+            <Text style={styles.showInfoDesc}>
+              {preview}
+              {rest && !showInfoExpanded ? " " : ""}
+              {rest && !showInfoExpanded ? (
+                <Text
+                  style={styles.showInfoMore}
+                  onPress={() => setShowInfoExpanded(true)}
+                >
+                  more
+                </Text>
+              ) : null}
+              {rest && showInfoExpanded ? ` ${rest}` : ""}
+              {rest && showInfoExpanded ? (
+                <Text
+                  style={styles.showInfoMore}
+                  onPress={() => setShowInfoExpanded(false)}
+                >
+                  {" "}less
+                </Text>
+              ) : null}
+            </Text>
+            <Text style={styles.showInfoDisclaimer}>{info.disclaimerText}</Text>
+          </View>
+        );
+      })()}
+
       {egg ? (
         <Pressable style={styles.egg} onPress={() => setEgg(false)}>
           <Text style={styles.eggEmoji}>📟✨</Text>
@@ -1066,4 +1140,46 @@ const styles = StyleSheet.create({
     marginBottom: 10,
   },
   eggBody: { color: "#fff6ec", fontSize: 14, textAlign: "center", lineHeight: 21 },
+  // Show info banner (below the show selector)
+  showInfoBanner: {
+    paddingHorizontal: 14,
+    paddingTop: 6,
+    paddingBottom: 8,
+    backgroundColor: "rgba(123,63,110,0.06)",
+    borderBottomWidth: 1,
+    borderBottomColor: "rgba(123,63,110,0.10)",
+  },
+  showInfoChips: {
+    flexDirection: "row",
+    flexWrap: "wrap",
+    gap: 5,
+    marginBottom: 6,
+  },
+  showInfoChip: {
+    backgroundColor: "rgba(123,63,110,0.12)",
+    borderRadius: 10,
+    paddingHorizontal: 8,
+    paddingVertical: 3,
+  },
+  showInfoChipText: {
+    fontSize: 11,
+    fontWeight: "600",
+    color: "#5a3a5a",
+  },
+  showInfoDesc: {
+    fontSize: 12,
+    color: "#4a3a52",
+    lineHeight: 18,
+    marginBottom: 4,
+  },
+  showInfoMore: {
+    fontSize: 12,
+    color: "#7b3f6e",
+    fontWeight: "600",
+  },
+  showInfoDisclaimer: {
+    fontSize: 10,
+    color: "#999",
+    lineHeight: 14,
+  },
 });

← f7cb8f0 api/shows: add showInfo field (description, seasonCount, epi  ·  back to Nineoh Guide  ·  Wire real AdMob banner: iosAppId ca-app-pub-5278231299883833 182ad70 →