Skip to content
Draft
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
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ Working rules for this repo:

## Project notes

- **Product display name is "Solo Adventurer's Journal"** (in-app titles, platform
manifests, web title/manifest, README H1). This is DISTINCT from two things that
- **Product display name is "Loreseer"** (tagline "The oracle behind your
adventure"; in-app titles, platform manifests, web title/manifest, README H1).
Renamed from the former "Solo Adventurer's Journal"; see
`docs/superpowers/notes/2026-06-26-product-name-brainstorm.md`. This is DISTINCT
from two things that
intentionally keep the old "juice" identifier: (1) the **Juice oracle** — the
jrruethe/juice oracle *system* the app implements (the `'juice'`/`'Juice'` oracle
id, `fate-juice`, the "Juice oracle" opt-in system + badge) — never rename these;
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Solo Adventurer's Journal
# Loreseer

*The oracle behind your adventure.*

**Play it now:** https://taylor-software.github.io/juice/ — free, no account,
your campaigns stay on your device.
Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Third-party content & licenses

Solo Adventurer's Journal incorporates third-party tabletop content, oracle
Loreseer incorporates third-party tabletop content, oracle
tables, and artwork under the licenses below. This file is the canonical
inventory; in-app attributions live in **Settings → Third-party content**
(`lib/features/settings_sheet.dart`), and each vendored data set carries its
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Solo Adventurer's Journal"
android:label="Loreseer"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
Expand Down
Binary file modified android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icon/app_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions assets/icon/app_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions build_app_icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Loreseer app icon generator — source of truth for the app icon.

Concept: 'eye-in-open-book' — a gold eye (upper lid + radiant iris) above an
open book whose fanned pages form the lower lid, on a deep indigo/violet field.
Brand palette: deep indigo/violet field + warm gold (the mystical/oracle lane).

This script is the source of truth (like build_oracle.py et al): it writes the
re-editable vector master `assets/icon/app_icon.svg`, the 1024 raster master
`assets/icon/app_icon.png`, and every derived platform asset at the exact sizes
the project already ships (web icons + maskable + favicon, Android mipmaps, iOS
+ macOS appiconsets, Windows .ico). Edit this script, then:

pip install cairosvg Pillow # one-time (not part of the app's Dart stack)
python3 build_app_icon.py

`dart run flutter_launcher_icons` (configured in pubspec.yaml from
assets/icon/app_icon.png) reproduces the same platform assets from the PNG
master, so either path works; this script additionally keeps the SVG in sync.
"""
import io
import os
import cairosvg
from PIL import Image

ROOT = os.path.dirname(os.path.abspath(__file__))

# ---- palette ----
BG_CTR, BG_EDGE = "#3a2f80", "#14122e"
PARCH = "#f4e9cf"
GOLD_HI, GOLD, AMBER = "#ffe07a", "#f2c14e", "#c87b22"
PUPIL = "#1b1740"
PAGE_HI, PAGE_LO = "#fff6df", "#e7c878"

DEFS = f'''
<radialGradient id="bg" cx="50%" cy="42%" r="78%">
<stop offset="0%" stop-color="{BG_CTR}"/><stop offset="100%" stop-color="{BG_EDGE}"/>
</radialGradient>
<radialGradient id="iris" cx="50%" cy="45%" r="60%">
<stop offset="0%" stop-color="{GOLD_HI}"/><stop offset="55%" stop-color="{GOLD}"/>
<stop offset="100%" stop-color="{AMBER}"/>
</radialGradient>
<linearGradient id="pageL" x1="0" y1="0" x2="1" y2="0.3">
<stop offset="0%" stop-color="{PAGE_LO}"/><stop offset="100%" stop-color="{PAGE_HI}"/>
</linearGradient>
<linearGradient id="pageR" x1="1" y1="0" x2="0" y2="0.3">
<stop offset="0%" stop-color="{PAGE_LO}"/><stop offset="100%" stop-color="{PAGE_HI}"/>
</linearGradient>
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="{GOLD_HI}" stop-opacity="0.55"/>
<stop offset="100%" stop-color="{GOLD_HI}" stop-opacity="0"/>
</radialGradient>'''

BG = f'''
<rect width="1024" height="1024" fill="url(#bg)"/>
<g fill="{PARCH}" opacity="0.5">
<circle cx="210" cy="190" r="4"/><circle cx="820" cy="230" r="5"/>
<circle cx="160" cy="760" r="4"/><circle cx="860" cy="800" r="4"/>
<circle cx="300" cy="120" r="3"/><circle cx="700" cy="140" r="3"/>
</g>'''

FG = f'''
<circle cx="512" cy="438" r="210" fill="url(#glow)"/>
<!-- upper lid -->
<path d="M168,452 C 330,292 694,292 856,452 C 700,372 324,372 168,452 Z"
fill="url(#pageL)" stroke="{AMBER}" stroke-width="6" stroke-linejoin="round"/>
<path d="M210,420 C 350,318 674,318 814,420"
fill="none" stroke="{GOLD}" stroke-width="7" stroke-linecap="round" opacity="0.65"/>
<!-- iris -->
<circle cx="512" cy="446" r="126" fill="url(#iris)"/>
<circle cx="512" cy="446" r="55" fill="{PUPIL}"/>
<circle cx="490" cy="424" r="18" fill="{PAGE_HI}" opacity="0.92"/>
<g stroke="{AMBER}" stroke-width="6" opacity="0.5" stroke-linecap="round">
<line x1="512" y1="338" x2="512" y2="372"/><line x1="386" y1="446" x2="420" y2="446"/>
<line x1="604" y1="446" x2="638" y2="446"/><line x1="426" y1="368" x2="448" y2="390"/>
<line x1="598" y1="368" x2="576" y2="390"/>
</g>
<!-- open book -->
<path d="M512,556 C 430,536 280,532 162,516 L 150,552 C 270,616 420,668 512,694 Z"
fill="url(#pageL)" stroke="{AMBER}" stroke-width="5.5" stroke-linejoin="round"/>
<path d="M512,556 C 594,536 744,532 862,516 L 874,552 C 754,616 604,668 512,694 Z"
fill="url(#pageR)" stroke="{AMBER}" stroke-width="5.5" stroke-linejoin="round"/>
<path d="M512,556 L512,694" stroke="{AMBER}" stroke-width="8" stroke-linecap="round"/>
<g stroke="{AMBER}" stroke-width="4.5" opacity="0.55" stroke-linecap="round" fill="none">
<path d="M232,558 C 330,602 430,628 498,640"/><path d="M210,592 C 318,640 426,664 498,674"/>
<path d="M792,558 C 694,602 594,628 526,640"/><path d="M814,592 C 706,640 598,664 526,674"/>
</g>'''


def svg(fg_scale=1.0):
fg = FG
if fg_scale != 1.0:
fg = (f'<g transform="translate(512,512) scale({fg_scale}) '
f'translate(-512,-512)">{FG}</g>')
return ('<?xml version="1.0" encoding="UTF-8"?>'
'<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" '
f'viewBox="0 0 1024 1024"><defs>{DEFS}</defs>{BG}{fg}</svg>')


def render(fg_scale=1.0, size=1024):
png = cairosvg.svg2png(bytestring=svg(fg_scale).encode(),
output_width=size, output_height=size)
return Image.open(io.BytesIO(png)).convert("RGBA")


def save(img, rel):
path = os.path.join(ROOT, rel)
os.makedirs(os.path.dirname(path), exist_ok=True)
img.save(path)


def main():
master = render(1.0, 1024)
save(master, "assets/icon/app_icon.png")
with open(os.path.join(ROOT, "assets/icon/app_icon.svg"), "w") as f:
f.write(svg(1.0))

def down(size):
return master.resize((size, size), Image.LANCZOS)

# maskable master: content scaled into the safe zone, full-bleed background
maskable = render(0.80, 1024)

# web
save(down(192), "web/icons/Icon-192.png")
save(down(512), "web/icons/Icon-512.png")
save(maskable.resize((192, 192), Image.LANCZOS), "web/icons/Icon-maskable-192.png")
save(maskable.resize((512, 512), Image.LANCZOS), "web/icons/Icon-maskable-512.png")
save(down(16), "web/favicon.png")

# android (legacy full-bleed launcher)
for d, s in [("mdpi", 48), ("hdpi", 72), ("xhdpi", 96),
("xxhdpi", 144), ("xxxhdpi", 192)]:
save(down(s), f"android/app/src/main/res/mipmap-{d}/ic_launcher.png")

# ios (flatten alpha — iOS icons must be opaque)
ios_dir = "ios/Runner/Assets.xcassets/AppIcon.appiconset"
for fn in os.listdir(os.path.join(ROOT, ios_dir)):
if fn.endswith(".png"):
s = Image.open(os.path.join(ROOT, ios_dir, fn)).size[0]
save(down(s).convert("RGB"), f"{ios_dir}/{fn}")

# macos
mac_dir = "macos/Runner/Assets.xcassets/AppIcon.appiconset"
for fn in os.listdir(os.path.join(ROOT, mac_dir)):
if fn.endswith(".png"):
s = Image.open(os.path.join(ROOT, mac_dir, fn)).size[0]
save(down(s), f"{mac_dir}/{fn}")

# windows .ico
master.save(os.path.join(ROOT, "windows/runner/resources/app_icon.ico"),
sizes=[(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (256, 256)])

print("generated all platform icon assets.")


if __name__ == "__main__":
main()
4 changes: 2 additions & 2 deletions docs/design-overview.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Solo Adventurer's Journal — Design Overview
# Loreseer — Design Overview

This document describes every major screen, feature, and configuration option in **Solo Adventurer's Journal** (internal id: `juice`). It is intended for designers who need a comprehensive map of the product's UI surfaces and options.
This document describes every major screen, feature, and configuration option in **Loreseer** (internal id: `juice`). It is intended for designers who need a comprehensive map of the product's UI surfaces and options.

All screenshots are in `docs/screenshots/design/`.

Expand Down
82 changes: 82 additions & 0 deletions docs/superpowers/notes/2026-06-26-product-name-brainstorm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Product name brainstorm — adopted: **Loreseer**

**Date:** 2026-06-26
**Status:** ADOPTED. The display name was renamed from *Solo Adventurer's Journal*
to **Loreseer** (tagline "The oracle behind your adventure") in this same change —
display strings only (in-app titles, platform manifests, web title/manifest,
README H1, pubspec/THIRD_PARTY/design-overview prose). Internal `juice` identifiers
are untouched per `CLAUDE.md`. This note records the exploration behind the choice.

## Brief

Rename target: the **display name only** (in-app titles, platform manifests, web
title/manifest, README H1). Per `CLAUDE.md`, the *Juice oracle* identity and the
internal `juice_oracle` / `juice.*` / `net.taylorsoftware.juice` identifiers stay
put — a rename touches display strings, not stable ids.

Chosen lane (from brainstorm with the maintainer):
- **Vibe:** mystical / evocative (north star was *Soothsayer*).
- **Brandable:** own the word, not descriptive-on-the-tin.
- **Angle:** lean into the **GM-tool / unseen-GM** framing (a feature being added).

## Front-runner: Loreseer

A coined compound — **lore·seer** — that reads on two levels at once:
*one who sees the unfolding story* and the *GM-as-oracle* role. The word already
carries fantasy resonance to players but has no commercial owner.

### Clearance findings (web check, 2026-06-26)

- **No product collision.** No app, game, or company called "Loreseer" on the App
Store, Steam, or itch. The word appears only as fantasy flavor (a Minecraft-server
faith title; a web-serial term).
- **Soft phonetic neighbors only:** *Lore Seeker* (a deck-building game, two words)
and *Loreseeker Games* (a studio making a mermaid game, *SIREN*). Different
spelling, different niche — not our lane.
- **Domain — RESOLVED.** Canonical/primary is **`loreseer.app`** (secured) — exact
word, app-native TLD, no hyphen or prefix. The exact-match `loreseer.com` is taken
(matched the inconclusive HTTP 403 during clearance), a minor traffic leak but not
a blocker. Available defensive options at the time of checking: `loreseer.online`,
`theloreseer.com`, `lore-seer.com` (none required now that `.app` is the front door;
hyphenated `.com` is weak as a primary regardless).

### Why it won the bake-off

Three finalists were clearance-tested:

| Name | TTRPG collision | Ownability | Verdict |
|---|---|---|---|
| **Loreseer** | none (only fantasy-flavor uses) | high (coined word) | **pick** — clear + brandable |
| **Sayer** | none in solo-RPG | low — common word + surname; `sayers.com` is a 40-yr IT consultancy; trademark/SEO crowding | runner-up if minimal/modern feel preferred |
| **Divinum** | **direct** — live action game on Steam (app 1148130), `divinumgame.com`, `@DivinumGame` | blocked — same industry, live brand owns domain/handle | retired |

## Creative direction (if adopted)

### Positioning
> **Loreseer** — your unseen GM. The app sees the story so you can live it.

### Taglines
- Hero (atmosphere): *"It sees what happens next."*
- Store subtitle (clarity): *"Solo & GM-less RPG companion."*
- Alternates: *"Every story has a seer." / "No GM? No problem." / "Your table of one."*

### Wordmark / styling
- Lowercase **`loreseer`** for the icon wordmark + UI; title-case **Loreseer** in prose.
- Emphasize the **lore·seer** seam (weight/color shift, or an iris motif in the "ee").
- Icon directions (fit the facts-only, no-rulebook-IP posture): an eye formed from an
open book; a die with an eye on its up-face; a keyhole/iris hybrid.
- Palette: deep indigo/violet + warm gold (prophecy/candlelight; also nods to the
light-timer mechanic). Avoid generic-fantasy green.

### Bonus: names the GM feature
The brand personifies the in-app AI GM seam as **"the Seer"** — *"Ask the Seer,"*
*"the Seer narrates,"* *"the Seer sees a complication."* Turns the product name into
the unseen-GM voice, which is exactly the framing the new GM angle wants.

## If/when adopted — rename checklist (display strings only)

- In-app titles, platform manifests (iOS/Android/macOS), web `index.html` title +
`manifest.json`, README H1.
- Do **NOT** touch: `'juice'`/`'Juice'` oracle id, `fate-juice`, `juice_oracle`
package, `juice.*` SharedPreferences keys, `juice-oracle` campaign marker,
`.juice.json`/`.juice.zip` extensions, `net.taylorsoftware.juice` bundle id.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solo Adventurer's Journal</string>
<string>Loreseer</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
Expand Down
2 changes: 1 addition & 1 deletion lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class JuiceApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final oracle = ref.watch(oracleProvider);
return MaterialApp(
title: "Solo Adventurer's Journal",
title: "Loreseer",
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
Expand Down
2 changes: 1 addition & 1 deletion lib/features/help_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class _HelpScreenState extends ConsumerState<HelpScreen> {
label: const Text('Software licenses'),
onPressed: () => showLicensePage(
context: context,
applicationName: "Solo Adventurer's Journal"),
applicationName: "Loreseer"),
),
],
],
Expand Down
4 changes: 2 additions & 2 deletions lib/features/launcher_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ class LauncherScreen extends ConsumerWidget {
padding: const EdgeInsets.all(24),
shrinkWrap: true,
children: [
Text('Juice', style: theme.textTheme.headlineMedium),
Text('Solo TTRPG toolkit',
Text('Loreseer', style: theme.textTheme.headlineMedium),
Text('The oracle behind your adventure',
style: theme.textTheme.bodyMedium
?.copyWith(color: theme.colorScheme.onSurfaceVariant)),
const SizedBox(height: 24),
Expand Down
4 changes: 2 additions & 2 deletions lib/shared/home_shell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,10 @@ class _HomeShellState extends ConsumerState<HomeShell> {
return Scaffold(
appBar: AppBar(
title: sessionName == null
? const Text("Solo Adventurer's Journal")
? const Text("Loreseer")
: Column(
children: [
const Text("Solo Adventurer's Journal"),
const Text("Loreseer"),
Text(sessionName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
Expand Down
Loading
Loading