merge main into packet/vertical

This commit is contained in:
Doxa Maps 2026-09-27 23:31:05 +00:00
commit 1a7724b3c1
714 changed files with 13009 additions and 5267 deletions

View file

@ -124,6 +124,56 @@ and graduates to a named tab when it's ready.
---
## Who it's for
### Vibe coders — who dx-maps was built for
If you build with AI agents, this repo is made for you:
- **One repo, many plugins.** Each plugin is one folder in `plugin-designs/` with its own `src/`,
and they all share one library, so your agents can build many plugins side by side.
- **See every design live.** `bun run dev` opens the Plugin Studio: every plugin in one place,
updating the moment you save.
- **Speak instead of typing.** Rudder, an offline voice-to-text tool, sits as a corner widget in the
Studio: look at a plugin, say what you see, and hand the text to your agent. Nothing leaves your
computer (Linux today).
- **One plugin, several outputs.** The same plugin ships as a web embed or a mobile app, and a plugin
can target the desktop: Rudder is the desktop example, running in the Studio today with its
desktop packaging next.
- **Build together.** [Run it in the cloud](#run-it-in-the-cloud) gives a whole team one shared dev
server.
### The people doing the mission
**Each map answers a real task for someone doing the mission.** Open it in the Research Gallery, or
embed just the one you need.
| You are… | Open | It answers |
|---|---|---|
| a **translator** (or an AI translation pipeline) | Gospel Resources (map, list, data tables) | which people groups have no Scripture, film or audio in their language, and which bridge language already reaches them |
| **media to movements** | Cities with Internet · Gospel × Internet | where unreached people groups live with internet access, country by country and down to province where data exists |
| a **catalyst** or mobilizer | Big Picture · Simple Research · Regions | filter the 2,107 groups by region, religion, language and engagement; **Export CSV** or **Export this selection**; open a group's **Full Profile** |
| **praying** | Prayer Progress · UPG-100 List | where prayer is covered and where it is still needed; print a **Prayer List**, **Poster Map** or **Card Deck** for a list of 100 |
| a **teacher** or presenter | the Research Gallery | every map in one app, in 11 languages, ready to show |
<details>
<summary><b>For AI agents</b></summary>
- **Every map, and how to embed it:** `public/dx-maps/manifest.json` lists each plugin with its
`embed.iframe` and `embed.script` snippet (with its integrity hash).
- **The data behind the maps:** `public/dx-maps/assets/tables/*.json` (gospel access, internet
access, bridge languages, resource links…), `assets/people-groups/*.json` (points and slim
records) and `assets/people-groups/lists/` (the membership of each list).
- **Live people-group data (public, no key):** `https://pray.doxa.life/api/people-groups/list`
and `…/detail/{slug}`, the same endpoints the published maps call.
- **Switches on any map page:** `?lang=` (11 languages), `?provider=maplibre|mapbox`,
`?list=` (a project list such as `700`), `?tk=` (a Mapbox token). Embeds take the same keys in
`profile-config`. Contract: [documentation/reference-universal-props.md](./documentation/reference-universal-props.md).
</details>
---
## The 5 plugin capabilities
A capability is **how much a plugin nests**, from one element (1) to an app of apps (5). Every
@ -438,6 +488,38 @@ bun run tauri:build # builds the app — needs Rust, the Tauri CLI and webvie
---
## Security controls
**Bundle integrity.** Every bundle ships with a SHA-384 hash in `public/dx-maps/manifest.json`
(`bundles[].integrity`), and every copy-paste script snippet (`profiles[].embed.script`) already
uses it:
```html
<script src="https://YOUR_HOST/dx-maps/doxa-simple-map/doxa-simple-map.js"
integrity="sha384-…" crossorigin="anonymous"></script>
```
If the file on the host is changed by anyone, even by one byte, the browser refuses to run it.
`crossorigin="anonymous"` is required for the check to work across sites, so the host must send
`Access-Control-Allow-Origin: *`. Copy the snippet again after each release, because the hash changes
with the bundle. `bun run check:sri` keeps the manifest and the built pages in step.
<details>
<summary><b>The backend in the cloud container (v1, experimental)</b></summary>
- **Locked by default.** A new PocketBase collection starts with admin-only rules; you open it
deliberately, collection by collection.
- **Dashboards hidden.** The production instance exposes only its `/api/`; its admin dashboard is
blocked at the proxy. The development dashboard is open only to the admin group.
- **No server-side code.** PocketBase's JavaScript hooks are switched off in both instances.
- **Not built yet:** per-plugin locks between plugin authors (who may edit which plugin's backend)
and isolated edge functions. Until they exist, everyone with dev access is trusted with every
plugin's backend.
</details>
---
## Languages
**Every map speaks 11 languages** — add `?lang=<code>` to a map's page address, or, when a map is embedded in
@ -671,3 +753,13 @@ doxa-maps/
> **Maps should be open source, run everywhere, and multiply.** One build makes each map a
> self-contained file for your site, your partners' sites and a phone. The Plugin Studio is where
> you compose them into something bigger.
---
## Acknowledgements
dx-maps stands on the work of open-source communities: [Vue](https://vuejs.org), [Vite](https://vite.dev),
[MapLibre GL JS](https://maplibre.org), [Pinia](https://pinia.vuejs.org), [Vue I18n](https://vue-i18n.intlify.dev),
[Capacitor](https://capacitorjs.com), [Tauri](https://tauri.app), [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy),
[NeMo-Speech.cpp](https://github.com/NVIDIA/NeMo-Speech.cpp) with NVIDIA's open [Nemotron speech model](https://huggingface.co/nvidia/nemotron-3.5-asr-streaming-0.6b), and the map data of
[OpenStreetMap](https://www.openstreetmap.org/copyright) contributors served by [OpenFreeMap](https://openfreemap.org). Thank you.

View file

@ -0,0 +1,13 @@
# PocketBase — the backend for plugins that need one
The cloud container ([deployment/cloud](../../deployment/cloud/README.md)) runs two PocketBase
instances: **dev** (behind the team sign-in, for building) and **prod** (its `/api/` public, for
published plugins). Both apply the migrations in [`pb_migrations/`](./pb_migrations/) on start, so
**the schema lives in git** and publishing never copies data from dev to prod.
A plugin reads its backend address at runtime (`profile-config` `apiBase`, or
`window.MAP_API_BASE` set by the published build's `runtime-config.js`), so moving the backend never
needs a rebuild.
v1 limits: collection rules are the only access control (lock collections by default); no per-plugin
isolation between plugin authors yet.

View file

@ -63,7 +63,8 @@ func envOr(k, d string) string {
func loadConfig() config {
home, _ := os.UserHomeDir()
base := filepath.Join(home, ".local", "share", "rudder")
// RUDDER_HOME is where setup-runtime.sh installed the runtime and model (a server keeps it on a volume).
base := envOr("RUDDER_HOME", filepath.Join(home, ".local", "share", "rudder"))
return config{
Port: envOr("RUDDER_PORT", "5193"),
NemoPort: envOr("RUDDER_NEMO_PORT", "5194"),
@ -181,9 +182,11 @@ func (e *engine) status() event {
e.mu.Lock()
defer e.mu.Unlock()
return event{
"engine": e.state, "error": e.stateErr, "recording": e.recording, "recorder": e.recorder,
// A browser session belongs to one reader; everyone else sees only that the engine is in use.
"engine": e.state, "error": e.stateErr, "recording": e.recording && !browserAudio(), "inUse": e.recording, "recorder": e.recorder,
"language": e.language, "defaultLanguage": e.cfg.Language, "device": e.device,
"runtime": "NVIDIA NeMo-Speech.cpp (ggml, CPU)", "model": filepath.Base(e.cfg.Model),
"audio": map[bool]string{true: "browser", false: "local"}[browserAudio()],
}
}
@ -284,7 +287,25 @@ func recorderCmds(device string) [][]string {
return cmds
}
func (e *engine) start(lang, device string) error {
// browserAudio: RUDDER_AUDIO=browser — the engine runs on a server (the cloud dev server), where there is
// no microphone of the reader's to record. The page captures its own mic and streams 16 kHz s16le PCM to
// /api/rudder/stream; that stream feeds the same pump and speech session as a local recorder.
func browserAudio() bool { return os.Getenv("RUDDER_AUDIO") == "browser" }
func (e *engine) start(lang, device string) error { return e.startWith(lang, device, nil, nil) }
// startWith starts a session. src, when set, is the audio source (a browser stream); nil spawns a local recorder.
func (e *engine) startWith(lang, device string, src *recorderProc, sink func(event)) error {
browser := src != nil
// A session's events go to ITS reader (a browser session's own socket), captured here, so a session
// still draining its last words after Stop never writes into the next reader's session.
emit := func(ev event) {
if sink != nil {
sink(ev)
return
}
e.hub.send(ev)
}
e.mu.Lock()
if e.state != "ready" {
e.mu.Unlock()
@ -292,6 +313,9 @@ func (e *engine) start(lang, device string) error {
}
if e.recording {
e.mu.Unlock()
if browser {
return errors.New("busy — someone else is recording on this server; try again when they stop")
}
return nil
}
if lang == "" {
@ -316,22 +340,25 @@ func (e *engine) start(lang, device string) error {
// mic link died 3-5 s into EVERY session that had speech (kernel: corrupted SCO packets); after
// switching to CVSD it ran 9.5 minutes without a drop. CVSD is 8 kHz, and Nemotron's transcript of an
// 8 kHz copy of the test clip differed by one word. RUDDER_KEEP_MSBC=1 opts out.
if card := btCard(device); card != "" && os.Getenv("RUDDER_KEEP_MSBC") == "" {
if card := btCard(device); !browser && card != "" && os.Getenv("RUDDER_KEEP_MSBC") == "" {
if p := currentProfile(card); p == "headset-head-unit-msbc" {
e.logs.add("bluetooth headset on mSBC — switching to CVSD before recording (stable voice link)")
e.hub.send(event{"type": "recovering", "message": "Switching your Bluetooth headset to its stable voice mode…"})
emit(event{"type": "recovering", "message": "Switching your Bluetooth headset to its stable voice mode…"})
if err := switchProfile(card, e.logs); err != nil {
e.logs.add("pre-switch to CVSD failed: %v", err)
} else {
e.hub.send(event{"type": "recovered", "message": "Headset is in its stable voice mode — go ahead and talk."})
emit(event{"type": "recovered", "message": "Headset is in its stable voice mode — go ahead and talk."})
}
}
}
first, err := spawnRecorder(device, e.logs)
if err != nil {
ws.Close()
return err
first := src
if first == nil {
first, err = spawnRecorder(device, e.logs)
if err != nil {
ws.Close()
return err
}
}
recName := first.name
var recMu sync.Mutex
@ -349,21 +376,39 @@ func (e *engine) start(lang, device string) error {
cur.kill()
recMu.Unlock()
writeMu.Lock()
// The streaming model holds back the last fraction of a second (its right context) until more audio
// or a pause arrives, and a commit alone does not flush it (rega: a Stop mid-sentence lost "what
// your country…"). 1.2 s of silence first lets it finish the words already spoken.
silence := make([]byte, 3200)
for i := 0; i < 12; i++ {
_ = ws.WriteMessage(websocket.BinaryMessage, silence)
}
_ = ws.WriteMessage(websocket.TextMessage, []byte(`{"type":"input_audio_buffer.commit"}`))
writeMu.Unlock()
select { // let the final words arrive
// Let the final words arrive. A shared server's CPU can run behind real time (measured: a loaded
// 8-core box finished an 11 s clip ~15 s after it ended), so a browser session waits longer.
wait := 4 * time.Second
if browser {
wait = 30 * time.Second
}
select {
case <-done:
case <-time.After(4 * time.Second):
case <-time.After(wait):
}
ws.Close()
emit(event{"type": "recording", "recording": false})
}
e.mu.Unlock()
e.logs.add("recording started: recorder=%s device=%q language=%s profile=%s", recName, device, lang, currentProfile(btCard(device)))
go watchKernelBluetooth(ctx, e.logs)
e.hub.send(event{"type": "recording", "recording": true, "recorder": recName, "language": lang, "device": device})
if browser {
e.logs.add("recording started: recorder=browser language=%s", lang)
} else {
e.logs.add("recording started: recorder=%s device=%q language=%s profile=%s", recName, device, lang, currentProfile(btCard(device)))
go watchKernelBluetooth(ctx, e.logs)
}
emit(event{"type": "recording", "recording": true, "recorder": recName, "language": lang, "device": device})
for _, d := range e.devs.current().Devices {
if (d.ID == device || (device == "" && d.Default)) && d.Muted {
e.hub.send(event{"type": "warning", "message": "Your microphone \"" + d.Label + "\" is muted in the system sound settings — unmute it or nothing will be heard."})
emit(event{"type": "warning", "message": "Your microphone \"" + d.Label + "\" is muted in the system sound settings — unmute it or nothing will be heard."})
}
}
@ -400,6 +445,12 @@ func (e *engine) start(lang, device string) error {
}
continue
}
if browser {
e.logs.add("browser audio stream ended: %v", err)
emit(event{"type": "error", "message": "The browser stopped sending microphone audio (the page was closed or lost its connection)."})
go e.stop()
return
}
// The recorder died on its own (bad device, sound server unreachable, device unplugged).
// End the session so the UI stops showing "Listening", and say WHY in plain words.
_ = r.cmd.Wait()
@ -415,7 +466,7 @@ func (e *engine) start(lang, device string) error {
if why != "" {
msg += " (" + r.name + ": " + why + ")"
}
e.hub.send(event{"type": "error", "message": msg})
emit(event{"type": "error", "message": msg})
go e.stop()
return
}
@ -434,7 +485,7 @@ func (e *engine) start(lang, device string) error {
// transcribed, then literal zeros while parec stayed "healthy" — the old Rudder's BUG 1,
// the headset losing the mic side of the link. Re-opening the source restarts the audio
// stream; the speech session and the transcript carry on untouched.
if peak == 0 {
if peak == 0 && !browser {
if silentSince.IsZero() {
silentSince = time.Now()
} else if !gaveUp && !resetting.Load() && time.Since(silentSince) > 1500*time.Millisecond {
@ -443,7 +494,7 @@ func (e *engine) start(lang, device string) error {
case recoveries == 1:
// Step 1: reopen the recorder (cheap; fixes a stalled stream).
e.logs.add("mic link dead (exact zeros 1.5s) — step 1: reopening the microphone")
e.hub.send(event{"type": "recovering", "attempt": recoveries, "message": "Microphone went silent — reconnecting it…"})
emit(event{"type": "recovering", "attempt": recoveries, "message": "Microphone went silent — reconnecting it…"})
if next, err := spawnRecorder(device, e.logs); err == nil {
recMu.Lock()
old := cur
@ -458,12 +509,12 @@ func (e *engine) start(lang, device string) error {
// "detect first", so it can't decide a dead link is fine.
card := btCard(device)
e.logs.add("mic still dead — step 2: forcing a new voice link: %s → headset-head-unit-cvsd", card)
e.hub.send(event{"type": "recovering", "attempt": recoveries, "message": "Microphone still silent — switching your Bluetooth headset to its more stable voice mode…"})
emit(event{"type": "recovering", "attempt": recoveries, "message": "Microphone still silent — switching your Bluetooth headset to its more stable voice mode…"})
resetting.Store(true)
go func() {
defer resetting.Store(false)
if err := switchProfile(card, e.logs); err != nil {
e.hub.send(event{"type": "warning", "message": "Couldn't switch the headset's voice mode: " + err.Error()})
emit(event{"type": "warning", "message": "Couldn't switch the headset's voice mode: " + err.Error()})
return
}
if next, err := spawnRecorder(device, e.logs); err == nil {
@ -478,7 +529,7 @@ func (e *engine) start(lang, device string) error {
case recoveries == 3 && btResetPath() != "":
// Step 3: full headset reset (disconnect → restart PipeWire → reconnect).
e.logs.add("mic still dead — step 3: full headset reset via RUDDER_BT_RESET")
e.hub.send(event{"type": "recovering", "attempt": recoveries, "message": "Still silent — fully resetting your Bluetooth headset (about 15 seconds)…"})
emit(event{"type": "recovering", "attempt": recoveries, "message": "Still silent — fully resetting your Bluetooth headset (about 15 seconds)…"})
resetting.Store(true)
go func() {
defer resetting.Store(false)
@ -487,7 +538,7 @@ func (e *engine) start(lang, device string) error {
return
}
if !ok {
e.hub.send(event{"type": "warning", "message": "The headset reset didn't bring the mic back — is the headset on and in range?"})
emit(event{"type": "warning", "message": "The headset reset didn't bring the mic back — is the headset on and in range?"})
}
if next, err := spawnRecorder(device, e.logs); err == nil {
recMu.Lock()
@ -505,7 +556,7 @@ func (e *engine) start(lang, device string) error {
if btResetPath() == "" {
msg += " (No headset-reset command is configured — see RUDDER_BT_RESET.)"
}
e.hub.send(event{"type": "warning", "message": msg})
emit(event{"type": "warning", "message": msg})
}
silentSince = time.Now()
}
@ -514,7 +565,7 @@ func (e *engine) start(lang, device string) error {
recovering.Store(false)
healthySince = time.Now()
e.logs.add("mic recovered after %d reconnect(s)", recoveries)
e.hub.send(event{"type": "recovered", "message": "Microphone is back — keep talking."})
emit(event{"type": "recovered", "message": "Microphone is back — keep talking."})
}
if recoveries > 0 && !healthySince.IsZero() && time.Since(healthySince) > 30*time.Second {
recoveries, gaveUp, healthySince = 0, false, time.Time{}
@ -530,14 +581,14 @@ func (e *engine) start(lang, device string) error {
}
if time.Since(lastLevel) > 150*time.Millisecond {
lastLevel = time.Now()
e.hub.send(event{"type": "level", "peak": float64(peak) / 32768})
emit(event{"type": "level", "peak": float64(peak) / 32768})
}
writeMu.Lock()
err := ws.WriteMessage(websocket.BinaryMessage, frame)
writeMu.Unlock()
if err != nil {
if ctx.Err() == nil {
e.hub.send(event{"type": "error", "message": "speech server connection lost: " + err.Error()})
emit(event{"type": "error", "message": "speech server connection lost: " + err.Error()})
}
return
}
@ -547,6 +598,7 @@ func (e *engine) start(lang, device string) error {
// Result reader: map the server's events onto the plugin's small vocabulary.
go func() {
defer close(done)
committed := false
for {
_, msg, err := ws.ReadMessage()
if err != nil {
@ -560,17 +612,28 @@ func (e *engine) start(lang, device string) error {
text := firstString(m, "transcript", "delta", "text")
switch {
case strings.HasSuffix(t, "input_audio_transcription.delta"):
if committed {
_ = ws.SetReadDeadline(time.Now().Add(6 * time.Second))
}
// Deltas APPEND to the utterance in progress; the server sends many empty ones.
if text != "" {
e.hub.send(event{"type": "partial", "text": text})
emit(event{"type": "partial", "text": text})
}
case strings.HasSuffix(t, "input_audio_transcription.completed"):
e.hub.send(event{"type": "final", "text": text, "at": time.Now().Format(time.RFC3339)})
emit(event{"type": "final", "text": text, "at": time.Now().Format(time.RFC3339)})
if committed {
return // the last utterance after Stop is in
}
case t == "error":
e.logs.add("speech server error: %s", string(msg))
e.hub.send(event{"type": "error", "message": string(msg)})
emit(event{"type": "error", "message": string(msg)})
case t == "input_audio_buffer.committed":
return
// The commit acknowledges Stop; the words for the audio before it can still follow (rega: a
// Stop mid-word lost "what your country…"). Keep reading until the final text or 6 s of quiet (a loaded server can take seconds per backlog chunk).
committed = true
e.logs.add("speech server committed the stream; reading its last words")
_ = ws.SetReadDeadline(time.Now().Add(6 * time.Second))
continue
default:
e.logs.add("speech event: %s", t)
}
@ -585,9 +648,13 @@ type recorderProc struct {
out io.ReadCloser
name string
errBuf *bytes.Buffer
closer io.Closer // a browser source: closing it ends the stream
}
func (r *recorderProc) kill() {
if r != nil && r.closer != nil {
_ = r.closer.Close()
}
if r != nil && r.cmd != nil && r.cmd.Process != nil {
_ = r.cmd.Process.Kill()
go func() { _ = r.cmd.Wait() }()
@ -640,9 +707,8 @@ func (e *engine) stop() {
if !was || stop == nil {
return
}
stop()
e.logs.add("recording stopped")
e.hub.send(event{"type": "recording", "recording": false})
e.logs.add("recording stopping")
stop() // sends "recording false" to the session's own reader once its last words are in
}
func (e *engine) shutdown() {
@ -670,6 +736,18 @@ func writeJSON(w http.ResponseWriter, code int, v any) {
// - CORS: only the desktop webview's origin (tauri://localhost, or http://tauri.localhost on Windows)
// may read responses cross-origin. The Plugin Studio needs none: it reaches /api/rudder through the
// dev server's same-origin proxy. RUDDER_ALLOW_ORIGIN adds one more exact origin (tests).
//
// allowedHost: RUDDER_ALLOW_HOST (comma-separated) names the public host of a server deployment, whose own
// sign-in (oauth2-proxy) sits in front of the dev server that proxies /api/rudder here. Unset = loopback only.
func allowedHost(host string) bool {
for _, h := range strings.Split(os.Getenv("RUDDER_ALLOW_HOST"), ",") {
if h = strings.TrimSpace(h); h != "" && h == host {
return true
}
}
return false
}
func guard(next http.Handler) http.Handler {
extra := os.Getenv("RUDDER_ALLOW_ORIGIN")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -677,7 +755,7 @@ func guard(next http.Handler) http.Handler {
if i := strings.LastIndex(host, ":"); i >= 0 && !strings.HasSuffix(host, "]") {
host = host[:i]
}
if host != "127.0.0.1" && host != "localhost" && host != "[::1]" {
if host != "127.0.0.1" && host != "localhost" && host != "[::1]" && !allowedHost(host) {
http.Error(w, "forbidden host", http.StatusForbidden)
return
}
@ -721,6 +799,10 @@ func main() {
Device string `json:"device"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
if browserAudio() {
writeJSON(w, 409, event{"error": "this engine takes the browser's microphone: stream to /api/rudder/stream"})
return
}
if err := e.start(body.Language, body.Device); err != nil {
e.logs.add("start failed: %v", err)
writeJSON(w, 409, event{"error": err.Error()})
@ -728,6 +810,53 @@ func main() {
}
writeJSON(w, 200, e.status())
})
// /api/rudder/stream (RUDDER_AUDIO=browser): a WebSocket of binary 16 kHz mono s16le PCM frames from the
// page's microphone. Opening it starts a session, closing it stops one; the text comes back on /events.
// The upgrader's default origin check (Origin must match Host) keeps other sites out.
upgrader := websocket.Upgrader{ReadBufferSize: 8192}
mux.HandleFunc("/api/rudder/stream", func(w http.ResponseWriter, r *http.Request) {
if !browserAudio() {
writeJSON(w, 409, event{"error": "this engine records a local microphone (RUDDER_AUDIO is not browser)"})
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
pr, pw := io.Pipe()
src := &recorderProc{out: pr, name: "browser", errBuf: &bytes.Buffer{}, closer: pr}
var wmu sync.Mutex
sink := func(ev event) {
b, _ := json.Marshal(ev)
wmu.Lock()
_ = conn.WriteMessage(websocket.TextMessage, b)
wmu.Unlock()
}
if err := e.startWith(r.URL.Query().Get("language"), "", src, sink); err != nil {
e.logs.add("stream start failed: %v", err)
_ = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(4409, err.Error()))
return
}
for {
t, b, err := conn.ReadMessage()
if err != nil {
break
}
if t != websocket.BinaryMessage {
// {"type":"stop"}: end the session but keep the socket open, so the last words (sent after the
// speech server's commit) still reach this reader; the page closes the socket after "recording false".
if bytes.Contains(b, []byte(`"stop"`)) {
go e.stop()
}
continue
}
if _, err := pw.Write(b); err != nil {
break // the session stopped (its pipe was closed)
}
}
e.stop()
})
mux.HandleFunc("/api/rudder/stop", func(w http.ResponseWriter, r *http.Request) {
e.stop()
writeJSON(w, 200, e.status())

1
data/tables/card-00.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-01.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-02.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-03.json Normal file
View file

@ -0,0 +1 @@
{"ani":{"row":["Ani",23.755117,103.52517,"CHN","yix",5100,1,"ani"],"c":{"l":"Axi","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/yix","n":"Axi"}},"country":"People's Republic of China","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/ani?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/yix":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/4983?language=Axi)","at":"2026-09-24T18:32:59.196Z"}},"sun":null},"bhatu-andamani":{"row":["Bhatu (Andamani)",11.623377,92.726483,"IND","hca",400,1,"bhatu-andamani"],"c":{"l":"Andaman Hindi Creole","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/hca","n":"Andaman Hindi Creole"}},"country":"India","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/bhatu-andamani?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/hca":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/19048?language=Andaman%20Hindi%20Creole)","at":"2026-09-24T19:37:20.449Z"}},"sun":null},"kalmyk-oirats":{"row":["Kalmyk-Oirats",48.584897,95.431934,"MNG","xal",189000,2,"kalmyk-oirats"],"c":{"l":"Kalmyk-Oirat","f":"https://jesusfilm.org/watch/jesus/kalmyk-oirat","a":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/XALIBT","n":"Institute for Bible Translation, Moscow","ln":"Kalmyk-Oirat","m":"audio, text, video","vsc":"36 parts, Эклц to АПОКАЛИПСИС"},"sx":{"u":"https://live.bible.is/bible/XALIBT","n":"Institute for Bible Translation, Moscow","src":"Faith Comes By Hearing","parts":36}},"country":"Mongolia","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/kalmyk-oirats?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://jesusfilm.org/watch/jesus/kalmyk-oirat":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/kalmyk-oirat.html)","at":"2026-09-24T12:25:45.542Z"},"https://live.bible.is/bible/XALIBT":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:25:44.855Z"}},"sun":null},"kasseng":{"row":["Kasseng",15.25616,107.35139,"LAO","tdf",1200,1,"kasseng"],"c":{"l":"Talieng","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/tdf","n":"Talieng"}},"country":"Lao People's Democratic Republic","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/kasseng?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/tdf":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/17109?language=Talieng)","at":"2026-09-24T20:15:38.099Z"}},"sun":null}}

1
data/tables/card-04.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-05.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-06.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-07.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-08.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-09.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-0f.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-10.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-11.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-12.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-13.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-14.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-15.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-16.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-17.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-18.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-19.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-1f.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-20.json Normal file
View file

@ -0,0 +1 @@
{"deaf-hong-kong":{"row":["Deaf of Hong Kong",22.27819894,114.16930265,"HKG","hks",7900,0,"deaf-hong-kong"],"c":{"l":"Hong Kong Sign Language","ck":"arclight (14 sign languages, all captured), fcbh (none)","un":"deaf_bible_society, door_international — both key-gated","sg":1},"country":"Hong Kong","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/deaf-hong-kong?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{},"sun":{"u":"https://sun.bible/sun/NewTestament","t":"SUN New Testament"}},"kono":{"row":["Kono",7.7533,-8.8226,"GIN","knu",121000,1,"kono"],"c":{"l":"Kono (Guinea)","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/knu","n":"Kono"}},"country":"Guinea","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/kono?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/knu":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/2530?language=Kono)","at":"2026-09-24T19:37:21.234Z"}},"sun":null},"male":{"row":["Male",5.8713,36.8208,"ETH","mdy",158000,2,"male"],"c":{"l":"Male","s":"https://ebible.org/mdybse/","sv":"Maale NT","sf":0,"ss":"New Testament","f":"https://jesusfilm.org/watch/jesus/male","a":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/MDYBSE","n":"2007 Bible Society of Ethiopia","ln":"Maale","m":"audio, text, video","vsc":"66 parts, ማዢንቲ ማፃኣፔ to ዮሃኒሴም ፆኦሲ ዻዌ ባኮ"},"cr":{"o":"Bible Society of Ethiopia","y":"2007"}},"country":"Ethiopia","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/male?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://ebible.org/mdybse/":{"state":"verified","detail":"HTTP 200","at":"2026-09-24T12:25:41.839Z"},"https://jesusfilm.org/watch/jesus/male":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/male.html)","at":"2026-09-24T12:25:42.498Z"},"https://live.bible.is/bible/MDYBSE":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:25:42.572Z"}},"sun":null},"sandhai-muslims":{"row":["Sandhai Muslims",21.523339,70.45605,"IND","kfr",1500,1,"sandhai-muslims"],"c":{"d":[{"s":"Open Bible Stories","u":"https://git.door43.org/OBS-TLF/kfr_obs"}],"l":"Kacchi","f":"https://jesusfilm.org/watch/jesus/kachchhi","o":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)"},"country":"India","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/sandhai-muslims?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://jesusfilm.org/watch/jesus/kachchhi":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/kachchhi.html)","at":"2026-09-24T12:25:49.907Z"},"https://git.door43.org/OBS-TLF/kfr_obs":{"state":"verified","detail":"HTTP 200","at":"2026-09-24T12:25:49.526Z"}},"sun":null}}

1
data/tables/card-21.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-22.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-23.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-24.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-25.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-26.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-27.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-28.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-29.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-2f.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-30.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-31.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-32.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-33.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-34.json Normal file
View file

@ -0,0 +1 @@
{"lati-vietnam":{"row":["Lati",22.80149,104.85041,"VNM","lbt",13000,0,"lati-vietnam"],"c":{"l":"Lachi","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion"},"country":"Vietnam","bridge":null,"bridge_candidate":{"l":"Vietnamese","c":"vie","pct":86,"official":true,"src":"cldr"},"pray_url":"https://pray.doxa.life/lati-vietnam?source=doxalife","_cardRes":{"bridge":null,"candidate":{"iso":"vie","language":"Vietnamese","credit":null,"scripture_url":"https://ebible.org/vie1934/","scripture_title":"Vietnamese Bible 1923","film_url":"https://jesusfilm.org/watch/jesus/vietnamese","film_title":"","zume_url":"https://zume.training/vi"},"alt":null},"verified":{},"sun":null},"nyong":{"row":["Nyong",18.4174,98.9743,"THA","yno",14000,1,"nyong"],"c":{"l":"Yong","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/yno","n":"Yong"}},"country":"Thailand","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/nyong?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/yno":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/18600?language=Yong)","at":"2026-09-24T22:03:36.784Z"}},"sun":null},"saba":{"row":["Saba",11.30721,18.49058,"TCD","saa",3300,1,"saba"],"c":{"l":"Saba","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion","ga":{"u":"https://fivefish.org/saa","n":"Saba"}},"country":"Chad","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/saba?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://fivefish.org/saa":{"state":"verified","detail":"HTTP 200 (via https://fivefish.org/3108?language=Saba)","at":"2026-09-24T23:45:40.178Z"}},"sun":null},"tswa":{"row":["Tswa",-21.0315,31.4419,"ZWE","tsc",196000,1,"tswa"],"c":{"l":"Tswa","f":"https://jesusfilm.org/watch/jesus/tswa","a":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/TSCBSM","n":"1972 Bible Society of Mozambique","ln":"Xitshwa","m":"audio, video","vsc":"27 parts, S Mateus to Apocalipse"}},"country":"Zimbabwe","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/tswa?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://jesusfilm.org/watch/jesus/tswa":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/tswa.html)","at":"2026-09-24T12:25:44.210Z"},"https://live.bible.is/bible/TSCBSM":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:25:43.468Z"}},"sun":null}}

1
data/tables/card-35.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-36.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-37.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-38.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-39.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-3f.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-40.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-41.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-42.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-43.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-44.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-45.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-46.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-47.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-48.json Normal file
View file

@ -0,0 +1 @@
{"bashkardi":{"row":["Bashkardi",25.7053,58.8715,"IRN","bsg",7900,0,"bashkardi"],"c":{"l":"Bashkardi","ck":"arclight, door43, ebible, fcbh, grn, scriptureearth","un":"api_bible, dbl, progress_bible, youversion"},"country":"Islamic Republic of Iran","bridge":{"l":"Persian, Iranian","c":"pes","st":"moderate","n":5,"s":true,"f":true},"bridge_candidate":null,"pray_url":"https://pray.doxa.life/bashkardi?source=doxalife","_cardRes":{"bridge":{"iso":"pes","language":"Persian, Iranian","credit":{"o":"Biblica, Inc","y":"2022","u":"https://www.biblica.com/","g":"biblica"},"scripture_url":"https://ebible.org/pesopcb/","scripture_title":"Open Persian Contemporary Bible","film_url":"https://jesusfilm.org/watch/jesus/farsi-western","film_title":"","zume_url":"https://zume.training/fa"},"candidate":null,"alt":null},"verified":{},"sun":null},"bororo":{"row":["Bororo",-16.833333,-56.133333,"BRA","bor",2400,2,"bororo"],"c":{"l":"Borôro","s":"https://ebible.org/bor/","sv":"Borôro: Pao Kurireu Bataru Kurireu; O Novo Testamento (New Testament)","sf":0,"ss":"New Testament","f":"https://jesusfilm.org/watch/jesus/bororo","a":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/BORWYI","n":"Bororo - Wycliffe Bible Translators, Inc.","ln":"Bororo","m":"audio, text, video","vsc":"27 parts, Mateus to Boe Oto Padure Kejeboe Reo"},"se":{"u":"https://scriptureearth.org/data/bor/","k":["PDF","sab"]},"cr":{"o":"Wycliffe Bible Translators, Inc","y":"2020","u":"https://www.wycliffe.org/","g":"wycliffe"}},"country":"Brazil","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/bororo?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://ebible.org/bor/":{"state":"verified","detail":"HTTP 200","at":"2026-09-24T12:24:57.729Z"},"https://jesusfilm.org/watch/jesus/bororo":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/bororo.html)","at":"2026-09-24T12:24:58.501Z"},"https://live.bible.is/bible/BORWYI":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:24:57.619Z"},"https://scriptureearth.org/data/bor/":{"state":"verified","detail":"HTTP 200","at":"2026-09-24T17:38:04.642Z"}},"sun":null},"damami":{"row":["Damami",26.451475,74.63615,"IND","mtr",5900,2,"damami"],"c":{"d":[{"s":"Open Bible Stories","u":"https://git.door43.org/OBS-TLF/mtr_obs"}],"l":"Mewari","f":"https://jesusfilm.org/watch/jesus/mewari","a":1,"o":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/MTRNLC","n":"New Life Computer Institute","ln":"Mewari","m":"audio, text, video","vsc":"27 parts, मत्ती to परकासितवाक्य"},"sx":{"u":"https://live.bible.is/bible/MTRNLC","n":"New Life Computer Institute","src":"Faith Comes By Hearing","parts":27}},"country":"India","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/damami?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://jesusfilm.org/watch/jesus/mewari":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/mewari.html)","at":"2026-09-24T12:25:49.581Z"},"https://live.bible.is/bible/MTRNLC":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:25:48.240Z"},"https://git.door43.org/OBS-TLF/mtr_obs":{"state":"verified","detail":"HTTP 200","at":"2026-09-24T12:25:48.373Z"}},"sun":null},"pangwal-buddhists":{"row":["Pangwal Buddhists",33.15328,76.49206,"IND","pgg",2100,1,"pangwal-buddhists"],"c":{"l":"Pangwali","f":"https://jesusfilm.org/watch/jesus/pangwali","a":1,"ck":"arclight, door43, ebible, fcbh, scriptureearth","un":"api_bible, dbl, progress_bible, youversion, grn (not yet swept)","au":{"u":"https://live.bible.is/bible/PGGWIN","n":"Pangwali - Wycliffe India Version","ln":"Pangwali","m":"audio, video","vsc":"27 parts, Matthew to Revelation"}},"country":"India","bridge":null,"bridge_candidate":null,"pray_url":"https://pray.doxa.life/pangwal-buddhists?source=doxalife","_cardRes":{"bridge":null,"candidate":null,"alt":null},"verified":{"https://jesusfilm.org/watch/jesus/pangwali":{"state":"verified","detail":"HTTP 200 (via https://www.jesusfilm.org/watch/jesus.html/pangwali.html)","at":"2026-09-24T12:25:19.162Z"},"https://live.bible.is/bible/PGGWIN":{"state":"verified","detail":"fileset confirmed by live.bible.is /api/bibles (the page itself answers 200 for any id)","at":"2026-09-24T12:25:18.519Z"}},"sun":null}}

1
data/tables/card-49.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-4f.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-50.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-51.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-52.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-53.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-54.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-55.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-56.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-57.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-58.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-59.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5a.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5b.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5c.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5d.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5e.json Normal file

File diff suppressed because one or more lines are too long

1
data/tables/card-5f.json Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more