S2 (a1): compliance + approval pack
- /responsible-gambling rebuilt sincerely: 21+, 1-800-GAMBLER primary, 17-state resource list, warning signs, self-exclusion guidance, links to the existing /settings surfaces. No marketing adjacency. - /terms + /privacy honest drafts with entity placeholders ([ENTITY NAME], [STATE OF FORMATION], [ARBITRATION VENUE], [CONTACT EMAIL]); privacy sub-processors match reality (adds Sentry, honest PostHog description). - NEW /methodology: pipeline -> engine -> letter grades, refusals, VYNDR Originals, ledger settle + CLV + n>=20, why misses are public. - /about audited to North Star framing (THE PROOF card, methodology link). - Footer: 1-800-GAMBLER + Methodology link; compliance audit found zero pages suppressing the global footer. - 5 Ghost seed articles in content/articles/ + docs/GHOST-PUBLISHING.md manual runbook (no Ghost access used). - tests/unit/compliancePages.test.js (repo source-text style). 2429 tests green, web build exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+33
-1
@@ -1,7 +1,39 @@
|
||||
# VYNDR — Build State
|
||||
|
||||
## Last Updated
|
||||
2026-07-10
|
||||
2026-07-11
|
||||
|
||||
## Session S2 (a1 board, 2026-07-11) — BUILT ✅ COMPLIANCE + APPROVAL PACK
|
||||
|
||||
Branch worktree off `day1/a1-board`. 2429 tests (206 suites), web build
|
||||
exit 0 (`next build --webpack`). No backend routes touched.
|
||||
- **/responsible-gambling REBUILT sincerely** — 21+, 1-800-GAMBLER
|
||||
(1-800-426-2537) as the primary helpline, 17-state resource list,
|
||||
warning signs, state self-exclusion guidance, links to the EXISTING
|
||||
/settings Responsible Play + Danger Zone surfaces. Zero marketing
|
||||
adjacency (no pricing/scan/upgrade links — a test enforces it).
|
||||
- **/terms + /privacy redrafted** as honest approval-pack drafts (marked
|
||||
DRAFT: JULY 2026). Entity details are placeholder tokens Kev must fill:
|
||||
[ENTITY NAME], [STATE OF FORMATION], [ARBITRATION VENUE],
|
||||
[CONTACT EMAIL]. Privacy sub-processors now match reality (Stripe,
|
||||
Supabase, Resend, Sentry; PostHog described honestly — autocapture off,
|
||||
banner is disclosure-only, NOT consent-gated).
|
||||
- **/methodology NEW** (server component + metadata, footer COMPANY link,
|
||||
OPEN_ROUTES): pipeline → engine → 11-step letter grades, refusals
|
||||
(insufficient_data), VYNDR Originals (41), Ledger settle (locked at
|
||||
grade time, box-score settle, CLV vs captured close, n≥20, public
|
||||
revisions), why misses are public.
|
||||
- **/about audited** to North Star framing: data intelligence platform,
|
||||
THE PROOF card (Ledger + n≥20 + refusals + methodology link). Kept the
|
||||
"give it back" h1 (Phase-E test) + Wordmark (QA.5).
|
||||
- **Footer**: helpline updated 1-800-522-4700 → 1-800-GAMBLER; Methodology
|
||||
link added. Compliance audit: footer is global in the root layout, no
|
||||
nested layout or CSS suppresses it on any page — zero pages missing it.
|
||||
- **content/articles/** — 5 Ghost-ready seed drafts (front matter, VOICE
|
||||
v1.1, zero exclamation points, test-enforced) + docs/GHOST-PUBLISHING.md
|
||||
manual runbook (no Ghost credentials used; nothing auto-posts).
|
||||
- Tests: tests/unit/compliancePages.test.js (31 assertions, source-text
|
||||
style).
|
||||
|
||||
## Current Phase
|
||||
SHIP BUILD v59.0 — Overnight session: ledger team/opponent addendum,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: How Streaks Lie
|
||||
slug: how-streaks-lie
|
||||
excerpt: Most hot streaks are soft schedules wearing a costume. How to read a streak against what it was built on, the way the VYNDR lens does.
|
||||
tags: [streaks, the-read-room]
|
||||
status: draft
|
||||
---
|
||||
|
||||
Most hot streaks are soft schedules wearing a costume.
|
||||
|
||||
A 7-game hit streak against 5.2 ERA pitching is not form. It is a calendar. The player did his job — but the streak's real author was the schedule maker, and tonight the schedule hands him a different assignment. Here is exactly how the trick works, and how to see through it.
|
||||
|
||||
## The raw streak is the industry's favorite lie
|
||||
|
||||
A streak, stated alone — "8 straight games over 19.5 points," "hits in 7 straight" — carries an implication: this will continue. Books and touts both love the raw number, for opposite reasons. Touts sell it as momentum. Books price it, correctly, as bait, and shade the line into the streak because they know the public buys continuation.
|
||||
|
||||
The number itself is true. The implication is the lie. Whether a streak continues has almost nothing to do with its length and almost everything to do with what it was built against and what it faces tonight.
|
||||
|
||||
## The three questions that expose a streak
|
||||
|
||||
**1. What was it built against?** Pull the opponents inside the streak window from the game log. A scoring streak assembled against bottom-ten defenses is a description of the schedule. The same streak built through two top-five defenses is information about the player.
|
||||
|
||||
**2. What does tonight look like?** A hit streak fed on soft right-handed pitching meets a 2.80 ERA starter tonight. The streak is 7 games old; the thing that produced it is not in the building. In MLB this is unusually concrete — you know the opposing starter by name, with his numbers, hours before first pitch.
|
||||
|
||||
**3. Has the price already eaten it?** By game 6 of a public streak, the line has moved. You are no longer betting on the player continuing; you are betting on him continuing at a worse number than anyone got last week. Streak buzz is negative closing line value sold as a story.
|
||||
|
||||
## The lens rule
|
||||
|
||||
On VYNDR, no raw streak ever renders alone. That is a hard rule in the code, not an editorial habit. Every streak row carries the opponents it was built against, tonight's matchup — including the opposing starter and his ERA for MLB bats — a difficulty rating of tonight versus the streak's diet, and a one-line read composing the three. When we cannot rate the matchup honestly, the row says less. We do not invent context to fill a box.
|
||||
|
||||
Sometimes the lens confirms the streak: built against real pitching, facing more of the same, line still fair. Those exist, and they are worth something precisely because most streaks fail the checks.
|
||||
|
||||
## The habit
|
||||
|
||||
Next time a streak headline reaches you, ask the three questions before the number impresses you. Who built it. Who is here tonight. What does it cost now. Magicians hate this. So do the books.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: How VYNDR Grades a Prop
|
||||
slug: how-vyndr-grades-a-prop
|
||||
excerpt: From the line feed to the letter. What actually happens inside the pipeline, including the part where the model refuses.
|
||||
tags: [methodology, the-read-room]
|
||||
status: draft
|
||||
---
|
||||
|
||||
Most grading products are a vibe wearing a letter. Here is what ours actually does.
|
||||
|
||||
## The pipeline, start to finish
|
||||
|
||||
Grades on VYNDR are produced by a scheduled pipeline, several times a day. Nothing is graded on demand, and nothing is graded twice at two different lines without both showing on the record.
|
||||
|
||||
The run starts with the current prop lines from sportsbook feeds — real numbers, captured with a timestamp. For each prop, the system builds a feature vector: recent form (last-5 and last-20 averages, and the variance between them), opponent strength against that exact stat, pace, home or away, rest days, schedule density, batter-versus-pitcher history for MLB, per-90 rates and expected-goals context for soccer.
|
||||
|
||||
Then the engine does the only thing that matters: it compares the model's projection to the posted line. In both directions. The over and the under are graded separately, and the side with more conviction survives. The output is an 11-step letter, F through A+, with a confidence score behind it.
|
||||
|
||||
## What a letter means
|
||||
|
||||
A letter grade is a probabilistic assessment of one side of one line at one moment. An A does not mean the bet wins. It means the gap between our projection and the book's number was wide, in our favor, at the timestamp on the entry.
|
||||
|
||||
The model is wrong regularly. Our public ledger shows exactly how regularly, misses listed by name in the same format as the wins. That is not a disclaimer we were forced to write. It is the design.
|
||||
|
||||
## The part nobody else advertises: refusals
|
||||
|
||||
If the pipeline cannot build a real projection — not enough recent games, no baseline rate, a data source down — the model refuses to grade. No letter renders. The prop shows an absent state, and a free read is not consumed.
|
||||
|
||||
This matters more than it sounds. A grade with nothing behind it still looks like a grade. It would fill the page, and it would be a lie with good typography. So the rule across the whole platform is simple: market numbers are captured, never computed. Model numbers are labeled as model output. Where the data is missing, the surface says less. Absent beats wrong.
|
||||
|
||||
## After the grade
|
||||
|
||||
Every grade is locked to the ledger at the moment it is made — line, odds, timestamp. After the games end, it settles against the real box score. Hit, miss, or push, recorded once. Alongside the result we track closing line value: whether the market moved toward our side or away from it between our timestamp and the close. That number, not the win-loss column, is the standard evidence of real edge.
|
||||
|
||||
And no percentage renders anywhere on VYNDR until at least 20 grades have settled. Below that the surface says RECORD BUILDING, because a percentage on 6 settles is a marketing number, and we do not publish marketing numbers.
|
||||
|
||||
The full methodology lives at vyndr.app/methodology. The record lives at vyndr.app/ledger. Draw your own conclusion.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "The VYNDR Originals: 41 Archetypes and Why They Exist"
|
||||
slug: the-vyndr-originals
|
||||
excerpt: TORCH, BOMBER, GHOST, CONDUCTOR. What the archetype system is, how a player earns one, and why it changes which props you should trust.
|
||||
tags: [archetypes, the-read-room]
|
||||
status: draft
|
||||
---
|
||||
|
||||
Every player profile on VYNDR carries a word like TORCH or BOMBER. This is not decoration. It is the compressed answer to a question the box score never asks: how does this player produce, and which of his props can you actually trust.
|
||||
|
||||
## What an archetype is
|
||||
|
||||
An archetype is a classification of production pattern, scored from a player's real season statistics. There are 41 VYNDR Originals across four sports — 15 for the NBA, 5 built specifically for the WNBA, 15 for MLB, and 6 for soccer. A player earns one by his numbers, not by reputation, and he re-earns it as the season moves. Nothing is assigned once and left to rot.
|
||||
|
||||
A few examples, so the vocabulary means something:
|
||||
|
||||
- **TORCH** — high-usage, shot-dependent scorer. His points track his touches almost one to one.
|
||||
- **CONDUCTOR** — the offense runs through him, for other people. Assists are the stable prop; his own scoring swings with shot selection.
|
||||
- **BOMBER** — the power bat. Home runs and total bases behave differently for him than for a contact hitter, and his strikeout risk rides along with the power.
|
||||
- **GHOST** — the speed threat. He beats you on the bases, in the gaps, on the margins the slow numbers miss.
|
||||
- **SURGE** — the usage sponge. His baseline is ordinary until a star sits, and then his points prop is a different prop entirely.
|
||||
|
||||
## Why it changes the read
|
||||
|
||||
Different archetypes have different reliable props and different volatile ones. Points for a TORCH is a stable leg because it is tied to a stable input, his usage. Assists for that same player float with game script. For a CONDUCTOR it is exactly reversed.
|
||||
|
||||
This is the practical content of the system: a 7.5 assist line means one thing hanging on a CONDUCTOR and another thing entirely hanging on a scorer who happened to have two good passing nights. Same number, different prop. The archetype tells you which one you are looking at.
|
||||
|
||||
Most players are not one thing, so a classification carries a primary archetype, an optional secondary, and a weighted blend. A player can be 70 percent TORCH and 30 percent CONDUCTOR, and his prop behavior will sit exactly where that mix predicts.
|
||||
|
||||
## Where it comes from
|
||||
|
||||
The classification runs on the same stats pipeline that feeds the grades — season averages, usage, rate stats, all from league data. When the model grades a prop for a BOMBER, the archetype is context the engine already holds, not a label bolted on afterward.
|
||||
|
||||
The names are ours. The industry has spent decades on labels like "volume scorer" and "floor general," which describe a player politely and tell you nothing about his prop sheet. The VYNDR Originals were named for the thing that matters: what the production pattern does to the numbers you are about to bet on.
|
||||
|
||||
Learn the vocabulary once and the slate starts reading differently. That is the point.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: "CLV: The Only Number That Proves an Edge"
|
||||
slug: what-clv-is
|
||||
excerpt: Closing line value in plain language. Why beating the close matters more than winning the bet, and how VYNDR captures it on every grade.
|
||||
tags: [clv, methodology, the-read-room]
|
||||
status: draft
|
||||
---
|
||||
|
||||
You can win a bet with a bad read and lose one with a great read. That is variance, and it is why the win-loss column — the number everyone stares at — is the slowest, noisiest possible way to judge a model. There is a faster, cleaner number. The industry calls it CLV: closing line value.
|
||||
|
||||
## The idea in one paragraph
|
||||
|
||||
The closing line is the book's number at the moment the game starts. It is the sharpest price of the day, because by then the market has absorbed every injury report, every lineup, and every dollar of smart money. If you consistently bet numbers that are better than where the line closes, you were consistently ahead of the market's final opinion — and over time, that is what winning is made of. Beating the close is the standard evidence of real edge. One result proves nothing. A season of positive CLV proves the process.
|
||||
|
||||
## A concrete example
|
||||
|
||||
VYNDR grades a total-bases over at a line of 1.5, odds -120, at 2:14 PM. By first pitch the same over is priced at -145. The market spent the afternoon agreeing with the read — it moved 25 cents toward our side after we posted. That is positive CLV, and it is positive whether the player finishes with 3 total bases or 0.
|
||||
|
||||
Now reverse it. The read settles as a win, but the close drifted to -105 — the market moved away from us and we happened to cash anyway. The result column says we were right. The CLV column says we were lucky. Over 20 bets you cannot tell those apart by results. Over 200, CLV has already told you.
|
||||
|
||||
## How VYNDR captures it
|
||||
|
||||
Every grade is locked to the ledger at grade time: line, odds, timestamp. From then until the game starts, the pipeline keeps re-capturing the book's current number on every pass, and the last capture before first pitch stands as the close. Real numbers, from the feed, at a timestamp — never estimated, never backfilled.
|
||||
|
||||
At settlement the ledger computes CLV signed by side: for an over, a close that moved lower means the market came toward the graded side, which counts as beating the close. For an under, the inverse. Each entry records it, and the aggregate renders only after the same rule every percentage on VYNDR obeys — at least 20 settled entries, or the surface says RECORD BUILDING.
|
||||
|
||||
## Why we lead with it
|
||||
|
||||
A results record can be dressed up. A timestamped CLV record cannot — it is the market itself grading our timing, entry by entry, in public. When the line moves three minutes after the wire posts, someone is watching. Good.
|
||||
|
||||
If you take one habit from this piece: judge any model — ours included — by whether it beats the close over a real sample. Everything else is a story about a hot week.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Why Our Misses Are Public
|
||||
slug: why-our-misses-are-public
|
||||
excerpt: Every miss stays on the Ledger, by name, in the same format as the wins, permanently. The two reasons, and why nobody else in the industry can copy this.
|
||||
tags: [the-ledger, methodology]
|
||||
status: draft
|
||||
---
|
||||
|
||||
Open the VYNDR Ledger on any morning and you will find last night's misses listed by name, in the same format, the same placement, and the same energy as the wins. Wilson over 23.5 settled at 19. It is on the record. It stays on the record.
|
||||
|
||||
This is the strangest thing about the product to people who know the industry, so it deserves a straight explanation.
|
||||
|
||||
## Reason one: a curated record is not a record
|
||||
|
||||
The entire value of a grading model rests on its record being real. Not the highlight reel — the record. The moment you delete one bad night, the whole thing becomes marketing, and every number on the page inherits the doubt.
|
||||
|
||||
So the mechanics make curation impossible rather than merely discouraged. Every grade is written to the ledger at the moment it is made, locked with the line, the odds, and a timestamp. Settled entries are never edited and never deleted. If the market moves hard against a grade during the day and the model revises, the original letter stays on the record, struck through, next to the revision. Nothing is revised silently.
|
||||
|
||||
And no percentage renders anywhere until at least 20 grades have settled. A hot 5-1 start is a coin flip wearing a suit. We do not publish it.
|
||||
|
||||
## Reason two: nobody else can afford to
|
||||
|
||||
The standard industry model is a performance. Post the wins loud, let the losses expire in the feed, and rebuild the persona every time the variance runs cold. It works, for a while, because most audiences never audit.
|
||||
|
||||
That model has one structural weakness: it cannot survive a public, timestamped, append-only record. Ours is built on one. Publishing the misses is not bravery — it is the one competitive position the performance accounts cannot follow us into. Their numbers do not survive daylight. Ours are built to live in it.
|
||||
|
||||
## What a miss actually is
|
||||
|
||||
A miss is not a scandal. The model grades probabilistically, which means it is wrong regularly, on purpose, as a condition of being right more often than the market's price implies. A 60 percent proposition misses 4 times in 10. The question that matters is not whether misses happen — it is whether the process that produced them was sound, and the number that answers that is closing line value, tracked on every entry, not the win-loss column.
|
||||
|
||||
When a high-profile read misses, we post it the same way we post everything: the number, the reason the read existed, and the result. No apology thread. No celebration either. The format is the apology and the flex at once.
|
||||
|
||||
The record talks. We don't have to.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Ghost Publishing Runbook — blog.vyndr.app
|
||||
|
||||
How to move the seed articles in `content/articles/` into Ghost as drafts.
|
||||
Manual by design: **nothing auto-posts, anywhere, ever** (NORTH STAR — Kev is
|
||||
the publisher of everything). Claude Code has no Ghost credentials and never
|
||||
touches the Ghost admin; this runbook is the entire publishing pipeline.
|
||||
|
||||
## What lives where
|
||||
|
||||
- Source of truth: `content/articles/*.md` in this repo. Edit here first,
|
||||
then re-paste into Ghost — never edit only in Ghost, or the repo copy rots.
|
||||
- Each file has YAML front matter (`title`, `slug`, `excerpt`, `tags`,
|
||||
`status: draft`). The front matter is metadata for YOU to key into Ghost's
|
||||
fields — do not paste the front matter block into the post body.
|
||||
|
||||
## Per-article steps (about 3 minutes each)
|
||||
|
||||
1. Open `https://blog.vyndr.app/ghost/` and sign in (your credentials).
|
||||
2. Click **New post**.
|
||||
3. Title: copy the `title` from the front matter into the title field.
|
||||
4. Body: in the editor type `/` and choose the **Markdown** card (or type
|
||||
```/markdown```). Paste everything BELOW the closing `---` of the front
|
||||
matter into the card. Ghost renders the `##` headings, bold, and lists.
|
||||
- Do not paste into the plain editor surface — straight pasting can
|
||||
flatten headings depending on Ghost version. The Markdown card is exact.
|
||||
5. Open **Post settings** (gear icon, top right):
|
||||
- **Post URL**: set to the front-matter `slug` (e.g. `what-clv-is`).
|
||||
- **Excerpt**: paste the front-matter `excerpt`.
|
||||
- **Tags**: add the front-matter tags (e.g. `methodology`, `the-read-room`).
|
||||
6. Leave the post as a **draft** — do NOT click Publish during seeding.
|
||||
7. Preview in Ghost. Checklist before it ever goes live:
|
||||
- Zero exclamation points (VOICE v1.1 — deadpan, no exceptions).
|
||||
- No banned vocabulary: "lock", "tail", "fade" as calls to action,
|
||||
guarantee language, hype emoji. If it sounds like the industry, cut it.
|
||||
- Every record/percentage claim obeys the product's n≥20 gate — if the
|
||||
live Ledger cannot back a number in the article, remove the number.
|
||||
- Links point at `vyndr.app/...` production paths (`/methodology`,
|
||||
`/ledger`), not localhost or staging.
|
||||
|
||||
## The five seed articles (in publish order)
|
||||
|
||||
| File | Title |
|
||||
| --- | --- |
|
||||
| `how-vyndr-grades-a-prop.md` | How VYNDR Grades a Prop |
|
||||
| `why-our-misses-are-public.md` | Why Our Misses Are Public |
|
||||
| `what-clv-is.md` | CLV: The Only Number That Proves an Edge |
|
||||
| `the-vyndr-originals.md` | The VYNDR Originals: 41 Archetypes and Why They Exist |
|
||||
| `how-streaks-lie.md` | How Streaks Lie |
|
||||
|
||||
Suggested cadence: one per week, methodology first (it is the page every
|
||||
other article links back to conceptually). Publishing is your call, on your
|
||||
schedule — this table is an ordering, not a scheduler.
|
||||
|
||||
## Updating a published article
|
||||
|
||||
1. Edit the markdown in `content/articles/` and commit.
|
||||
2. In Ghost, open the post, delete the old Markdown card, insert a fresh one,
|
||||
paste the updated body. Keep the same Post URL — never change a slug after
|
||||
publish (dead links).
|
||||
|
||||
## Explicitly out of scope
|
||||
|
||||
- No Ghost Admin API integration, no automation, no scheduled posts. If that
|
||||
ever changes it goes through a spec in `specs/` first and Kev still presses
|
||||
the button.
|
||||
- No exclamation points. Still.
|
||||
@@ -0,0 +1,189 @@
|
||||
// Session 2 (A1 board) — compliance + approval pack. The legal/trust surfaces
|
||||
// are asserted against their source text (plain-JS Jest config, no TS
|
||||
// transform — same pattern as vyndrAppShell.test.js).
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..', '..');
|
||||
const WEB = path.join(ROOT, 'web', 'src');
|
||||
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
|
||||
const readRoot = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
||||
const exists = (rel) => fs.existsSync(path.join(WEB, rel));
|
||||
|
||||
describe('S2 — /methodology page', () => {
|
||||
it('exists as a server component with metadata', () => {
|
||||
expect(exists('app/methodology/page.tsx')).toBe(true);
|
||||
const src = read('app/methodology/page.tsx');
|
||||
expect(src).not.toContain("'use client'");
|
||||
expect(src).toContain('export const metadata');
|
||||
});
|
||||
|
||||
const src = read('app/methodology/page.tsx');
|
||||
|
||||
it('covers the pipeline → engine → letter grades', () => {
|
||||
expect(src).toContain('The pipeline');
|
||||
expect(src).toContain('feature vector');
|
||||
expect(src).toMatch(/11-step letter grade/);
|
||||
expect(src).toContain('A+');
|
||||
});
|
||||
|
||||
it('covers refusals when the model has no projection', () => {
|
||||
expect(src).toContain('When the model refuses');
|
||||
expect(src).toContain('refuses to grade');
|
||||
expect(src).toContain('Absent beats wrong');
|
||||
});
|
||||
|
||||
it('covers the VYNDR Originals archetype system', () => {
|
||||
expect(src).toContain('VYNDR Originals');
|
||||
expect(src).toContain('41');
|
||||
['TORCH', 'BOMBER', 'GHOST', 'CONDUCTOR'].forEach((a) => expect(src).toContain(a));
|
||||
});
|
||||
|
||||
it('covers ledger settlement: lock at grade time, box-score settle, CLV, n≥20', () => {
|
||||
expect(src).toContain('How the Ledger settles');
|
||||
expect(src).toContain('locked with the exact line, the odds, and a timestamp');
|
||||
expect(src).toContain('real box score');
|
||||
expect(src).toContain('closing line');
|
||||
expect(src).toContain('n≥20');
|
||||
expect(src).toContain('RECORD BUILDING');
|
||||
});
|
||||
|
||||
it('covers why misses are public', () => {
|
||||
expect(src).toContain('Why the misses are public');
|
||||
expect(src).toContain('same format');
|
||||
});
|
||||
|
||||
it('states the not-a-sportsbook position and links the ledger', () => {
|
||||
expect(src).toContain('not a sportsbook');
|
||||
expect(src).toContain('/ledger');
|
||||
});
|
||||
|
||||
it('is registered as an open (ungated) route and linked from the footer', () => {
|
||||
const routes = require('../../web/src/lib/routes');
|
||||
expect(routes.OPEN_ROUTES).toContain('/methodology');
|
||||
expect(routes.isGatedRoute('/methodology')).toBe(false);
|
||||
expect(read('components/Footer.tsx')).toContain("href: '/methodology'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('S2 — /terms and /privacy drafts', () => {
|
||||
it('terms page exists with the non-negotiable disclosures', () => {
|
||||
const src = read('app/terms/page.tsx');
|
||||
expect(src).toContain('not a sportsbook');
|
||||
expect(src).toContain('21 years of age');
|
||||
expect(src).toContain('Stripe');
|
||||
expect(src).toContain('1-800-GAMBLER');
|
||||
});
|
||||
|
||||
it('privacy page exists with the real sub-processor list', () => {
|
||||
const src = read('app/privacy/page.tsx');
|
||||
expect(src).toContain('never sell');
|
||||
['Stripe', 'Supabase', 'PostHog', 'Resend', 'Sentry'].forEach((p) => expect(src).toContain(p));
|
||||
});
|
||||
|
||||
it('entity details are placeholder tokens, not invented facts', () => {
|
||||
const terms = read('app/terms/page.tsx');
|
||||
const privacy = read('app/privacy/page.tsx');
|
||||
['[ENTITY NAME]', '[STATE OF FORMATION]', '[ARBITRATION VENUE]', '[CONTACT EMAIL]'].forEach((t) =>
|
||||
expect(terms).toContain(t),
|
||||
);
|
||||
['[ENTITY NAME]', '[CONTACT EMAIL]'].forEach((t) => expect(privacy).toContain(t));
|
||||
// The previous draft asserted an unverified LLC + emails — keep them out
|
||||
// until Kev fills the placeholders.
|
||||
[terms, privacy].forEach((src) => {
|
||||
expect(src).not.toContain('VYNDR Intelligence LLC');
|
||||
expect(src).not.toContain('legal@vyndr.app');
|
||||
expect(src).not.toContain('privacy@vyndr.app');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('S2 — /responsible-gambling (sincere rebuild)', () => {
|
||||
const src = read('app/responsible-gambling/page.tsx');
|
||||
|
||||
it('carries 1-800-GAMBLER as the primary helpline and the 21+ mark', () => {
|
||||
expect(src).toContain('1-800-GAMBLER');
|
||||
expect(src).toContain('tel:18004262537');
|
||||
expect(src).toContain('21+');
|
||||
});
|
||||
|
||||
it('lists state resources for the major legal states', () => {
|
||||
['Michigan', 'New Jersey', 'New York', 'Pennsylvania', 'Ohio', 'Illinois'].forEach((s) =>
|
||||
expect(src).toContain(s),
|
||||
);
|
||||
});
|
||||
|
||||
it('links the existing self-exclusion / settings surfaces', () => {
|
||||
expect(src).toContain('/settings');
|
||||
expect(src).toContain('Self-exclusion');
|
||||
});
|
||||
|
||||
it('links national support resources', () => {
|
||||
['ncpgambling.org', 'gamblersanonymous.org', 'gamtalk.org'].forEach((r) => expect(src).toContain(r));
|
||||
});
|
||||
|
||||
it('has no marketing adjacency (no pricing/upgrade/scan funnels)', () => {
|
||||
['/pricing', '/upgrade', '/scan', '__goPaywall'].forEach((bad) => expect(src).not.toContain(bad));
|
||||
});
|
||||
});
|
||||
|
||||
describe('S2 — compliance footer (global)', () => {
|
||||
it('the root layout mounts the single global Footer', () => {
|
||||
expect(read('app/layout.tsx')).toContain('<Footer />');
|
||||
});
|
||||
|
||||
it('the footer carries 21+, the RG link, and not-a-sportsbook', () => {
|
||||
const src = read('components/Footer.tsx');
|
||||
expect(src).toContain('21+');
|
||||
expect(src).toContain("href: '/responsible-gambling'");
|
||||
expect(src).toContain('not a sportsbook');
|
||||
expect(src).toContain('1-800-GAMBLER');
|
||||
});
|
||||
|
||||
it('no nested layout replaces the root layout (footer cannot be bypassed)', () => {
|
||||
// The only nested layout (player/[name]) must render children straight
|
||||
// through — it exists for metadata only.
|
||||
const nested = read('app/player/[name]/layout.tsx');
|
||||
expect(nested).toContain('return children');
|
||||
expect(nested).not.toContain('<html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('S2 — seed articles + Ghost runbook', () => {
|
||||
const ARTICLES = [
|
||||
'content/articles/how-vyndr-grades-a-prop.md',
|
||||
'content/articles/the-vyndr-originals.md',
|
||||
'content/articles/why-our-misses-are-public.md',
|
||||
'content/articles/what-clv-is.md',
|
||||
'content/articles/how-streaks-lie.md',
|
||||
];
|
||||
|
||||
it.each(ARTICLES)('%s exists with draft front matter', (rel) => {
|
||||
const src = readRoot(rel);
|
||||
expect(src.startsWith('---')).toBe(true);
|
||||
expect(src).toContain('title:');
|
||||
expect(src).toContain('status: draft');
|
||||
});
|
||||
|
||||
it.each(ARTICLES)('%s obeys VOICE v1.1 — zero exclamation points', (rel) => {
|
||||
expect(readRoot(rel)).not.toContain('!');
|
||||
});
|
||||
|
||||
it('the new user-facing pages obey VOICE v1.1 — zero exclamation points', () => {
|
||||
[
|
||||
'app/methodology/page.tsx',
|
||||
'app/responsible-gambling/page.tsx',
|
||||
'app/about/page.tsx',
|
||||
'app/terms/page.tsx',
|
||||
'app/privacy/page.tsx',
|
||||
].forEach((rel) => expect(read(rel)).not.toContain('!'));
|
||||
});
|
||||
|
||||
it('the Ghost runbook exists and stays manual (no auto-posting)', () => {
|
||||
const src = readRoot('docs/GHOST-PUBLISHING.md');
|
||||
expect(src).toContain('blog.vyndr.app');
|
||||
expect(src).toContain('draft');
|
||||
expect(src).toContain('nothing auto-posts');
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,16 @@
|
||||
import SectionHead from '@/components/vyndr/SectionHead';
|
||||
import { Wordmark } from '@/components/vyndr';
|
||||
|
||||
export const metadata = { title: 'About' };
|
||||
export const metadata = {
|
||||
title: 'About',
|
||||
description:
|
||||
'VYNDR is a sports data intelligence platform built in Detroit. The Ledger is the public record — misses included.',
|
||||
};
|
||||
|
||||
// Brand page in system voice (§3). Server component.
|
||||
// Session 2 (A1 board) — audited against NORTH STAR v1.1: data intelligence
|
||||
// platform, the Ledger as public proof, built in Detroit. VOICE v1.1 —
|
||||
// deadpan, numbers first, no exclamation points.
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<section style={{ maxWidth: 680, margin: '0 auto', padding: '40px 16px 96px' }}>
|
||||
@@ -17,23 +24,40 @@ export default function AboutPage() {
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
|
||||
<p className="mono" style={{ fontSize: 14.5, color: 'var(--text-1)', lineHeight: 1.7 }}>
|
||||
VYNDR is prop intelligence — a proprietary terminal that reads the context the books hide.
|
||||
Beyond the box score: form, matchup, usage, rest, the cascade when a star sits. Then a single
|
||||
grade, earned across 40+ factors, that tells you whether the number is worth taking.
|
||||
VYNDR is a sports data intelligence platform. The free layer is the complete picture —
|
||||
schedule, stats, game logs, streaks with the context they were built against, line
|
||||
comparison across books — always populated, always interpreted. The paid layer is the
|
||||
read: a letter grade earned across the model's full feature pipeline, archetypes,
|
||||
correlation-aware parlays, and closing-line intelligence.
|
||||
</p>
|
||||
<p className="mono" style={{ fontSize: 14.5, color: 'var(--text-1)', lineHeight: 1.7 }}>
|
||||
We are an analytics tool, not a sportsbook. We don't take wagers. We hand you the read and
|
||||
the kill conditions, and let you decide.
|
||||
We are an analytics tool, not a sportsbook. We take no wagers and hold no funds. We hand
|
||||
you the read and let you decide.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<SectionHead style={{ marginBottom: 12 }}>THE PROOF</SectionHead>
|
||||
<div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, padding: 18 }}>
|
||||
<p className="mono" style={{ fontSize: 13.5, color: 'var(--text-1)', lineHeight: 1.65, margin: 0 }}>
|
||||
Every grade the model produces is locked at grade time — line, odds, timestamp — and
|
||||
settled against the real box score on{' '}
|
||||
<a href="/ledger" style={{ color: 'var(--text-0)' }}>the Ledger</a>, misses included,
|
||||
in the same format as the wins. No percentage renders until at least 20 grades have
|
||||
settled. When the model has no projection, it refuses to grade rather than guess.
|
||||
How all of it works is written down, in plain language, on{' '}
|
||||
<a href="/methodology" style={{ color: 'var(--text-0)' }}>the methodology page</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionHead style={{ marginBottom: 12 }}>BUILT BY</SectionHead>
|
||||
<div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, padding: 18 }}>
|
||||
<div style={{ fontSize: 17, fontWeight: 800 }}>Kevon Butler</div>
|
||||
<div className="mono" style={{ fontSize: 12.5, color: 'var(--text-1)', marginTop: 4 }}>Founder · Detroit</div>
|
||||
<p className="mono" style={{ fontSize: 13.5, color: 'var(--text-1)', lineHeight: 1.65, marginTop: 12 }}>
|
||||
A glitch between a tech-genius mind and a love for sports. Built in Detroit, for everyone the
|
||||
books count on staying in the dark.
|
||||
The books employ floors full of quants. This is one terminal, built in Detroit, for
|
||||
everyone the books count on staying in the dark. Seems fair.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Methodology',
|
||||
description:
|
||||
'How VYNDR grades a prop, what archetypes are, how the Ledger settles, and why the misses are public. Plain language, no hype.',
|
||||
};
|
||||
|
||||
// Session 2 (A1 board) — the approval-pack methodology page. Server component.
|
||||
// Plain-language description of the real pipeline (feature vector → engine →
|
||||
// letter grade → locked ledger entry → box-score settle → CLV). Every claim
|
||||
// here matches the code; if the pipeline changes, change this page in the
|
||||
// same commit.
|
||||
|
||||
const h2: React.CSSProperties = { fontSize: 20, fontWeight: 700, marginBottom: 12, color: 'var(--text-0)' };
|
||||
const body: React.CSSProperties = { color: 'var(--text-1)', lineHeight: 1.75, fontSize: 15, marginBottom: 12 };
|
||||
const card: React.CSSProperties = {
|
||||
background: 'var(--bg-1)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 10,
|
||||
padding: '16px 18px',
|
||||
};
|
||||
|
||||
function Mono({ children }: { children: React.ReactNode }) {
|
||||
return <span className="mono" style={{ color: 'var(--text-0)' }}>{children}</span>;
|
||||
}
|
||||
|
||||
export default function MethodologyPage() {
|
||||
return (
|
||||
<section style={{ maxWidth: 720, margin: '0 auto', padding: '48px 16px 120px' }}>
|
||||
<header style={{ marginBottom: 32 }}>
|
||||
<h1 style={{ fontSize: 36, fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 8, color: 'var(--text-0)' }}>
|
||||
How the grades work.
|
||||
</h1>
|
||||
<p className="mono" style={{ fontSize: 12, color: 'var(--text-2)', letterSpacing: '0.05em' }}>
|
||||
METHODOLOGY · PLAIN LANGUAGE · NO HYPE
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<p style={{ ...body, fontSize: 16 }}>
|
||||
This page describes what actually happens between a sportsbook posting a line and a letter
|
||||
grade appearing on VYNDR. It is written so a claim made here can be checked against the
|
||||
Ledger. If a sentence on this page ever stops being true, the page is wrong and we fix it.
|
||||
</p>
|
||||
|
||||
{/* ── The pipeline ─────────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>The pipeline</h2>
|
||||
<p style={body}>
|
||||
Grades are produced by a scheduled pipeline, not on demand. Several times a day the
|
||||
system pulls the current prop lines from sportsbook feeds, then builds a feature vector
|
||||
for each prop — the numbers the model reasons over. Depending on the sport, that includes
|
||||
recent form (last-5 and last-20 averages, variance), opponent strength against the exact
|
||||
stat, pace, home or away, rest days, schedule density, batter-versus-pitcher history for
|
||||
MLB, and per-90 rates with expected-goals context for soccer.
|
||||
</p>
|
||||
<p style={body}>
|
||||
The engine compares the model's projection to the posted line — in both directions.
|
||||
It grades the over and the under separately and keeps whichever side it has more
|
||||
conviction in. The output is an 11-step letter grade, <Mono>F</Mono> through{' '}
|
||||
<Mono>A+</Mono>, with a confidence score behind it. The letter is a probabilistic
|
||||
assessment of one side of one line. It is not a prediction and it is never a guarantee.
|
||||
</p>
|
||||
<div style={card}>
|
||||
<p className="mono" style={{ fontSize: 12.5, color: 'var(--text-1)', lineHeight: 1.8, margin: 0 }}>
|
||||
LINE FEED → FEATURE VECTOR → PROJECTION vs LINE (BOTH SIDES) → LETTER GRADE → LOCKED TO
|
||||
THE LEDGER → SETTLED vs THE BOX SCORE
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Refusals ─────────────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>When the model refuses</h2>
|
||||
<p style={body}>
|
||||
If the pipeline cannot build a real projection for a prop — not enough recent games, no
|
||||
per-90 baseline, a data source down — the model refuses to grade it. No letter renders,
|
||||
the prop shows an absent state, and a free read is not consumed. A refusal is not a
|
||||
failure mode we hide; it is the design. A grade with nothing behind it would still look
|
||||
like a grade, and that is worse than no grade.
|
||||
</p>
|
||||
<p style={body}>
|
||||
The same rule applies to every number on the platform: market values (lines, odds,
|
||||
closes) are real book numbers captured at a timestamp, never computed by us. Model values
|
||||
are labeled as model output. Where data is missing, the surface says less.
|
||||
Absent beats wrong.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* ── Archetypes ───────────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Archetypes — the VYNDR Originals</h2>
|
||||
<p style={body}>
|
||||
Archetypes are VYNDR's proprietary classification of how a player produces — 41
|
||||
originals across NBA, WNBA, MLB, and soccer, built by scoring each player's real
|
||||
season statistics against production patterns. A <Mono>TORCH</Mono> is a high-usage,
|
||||
shot-dependent scorer whose points track his touches. A <Mono>BOMBER</Mono> is a power
|
||||
bat whose home-run and total-base props behave differently from a contact hitter's.
|
||||
A <Mono>GHOST</Mono> beats you with speed. A <Mono>CONDUCTOR</Mono> creates for everyone
|
||||
else first.
|
||||
</p>
|
||||
<p style={body}>
|
||||
The point is practical, not decorative: different archetypes have different reliable and
|
||||
volatile props. Points for a TORCH and assists for a CONDUCTOR are stable; the inverse
|
||||
legs swing with game script. Each classification carries a primary archetype, an optional
|
||||
secondary, and a weighted blend — because most players are not one thing. The
|
||||
classification comes from the same stats pipeline that feeds the grades, so an archetype
|
||||
is re-earned from real numbers, not assigned once and left to rot.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* ── The Ledger ───────────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>How the Ledger settles</h2>
|
||||
<p style={body}>
|
||||
Every grade the pipeline produces is written to the Ledger at the moment it is made,
|
||||
locked with the exact line, the odds, and a timestamp. That entry never changes. If the
|
||||
line moves hard against a graded side later in the day, the system may re-grade at the
|
||||
real new line — and when it does, the original letter stays on the record, struck
|
||||
through, next to the revision. Nothing is revised silently.
|
||||
</p>
|
||||
<p style={body}>
|
||||
After the games end, entries settle against the real box score — the stat the player
|
||||
actually produced, from league data. Hit, miss, or push, recorded once, idempotently.
|
||||
Alongside the result we record closing line value: the pipeline captures the closing
|
||||
line for each graded prop (the last pre-game snapshot of the book's number) and
|
||||
measures whether the market moved toward or away from the graded side between our
|
||||
timestamp and the close. Beating the close consistently is the standard evidence that a
|
||||
model is finding real edge rather than getting lucky, which is why we track it on every
|
||||
entry instead of just counting wins.
|
||||
</p>
|
||||
<div style={card}>
|
||||
<p className="mono" style={{ fontSize: 12.5, color: 'var(--text-1)', lineHeight: 1.8, margin: 0 }}>
|
||||
THE n≥20 RULE — no hit percentage or CLV percentage renders anywhere on VYNDR until at
|
||||
least 20 grades have settled in that window. Below that, the surface says
|
||||
RECORD BUILDING. A percentage on a small sample is a marketing number, and we do not publish
|
||||
marketing numbers.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Misses ───────────────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Why the misses are public</h2>
|
||||
<p style={body}>
|
||||
Every settled miss stays on the Ledger, by name, in the same format and the same
|
||||
placement as the wins, permanently. There are two reasons. The honest one: a record you
|
||||
curate is not a record, and the entire value of this product rests on the record being
|
||||
real. The practical one: nobody else in this industry can afford to do it, because their
|
||||
numbers do not survive it. Ours are built to. The model is wrong regularly — the Ledger
|
||||
shows exactly how regularly, and that transparency is the product working as intended,
|
||||
not a bug in it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* ── What we never do ─────────────────────────────────────── */}
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>What we never do</h2>
|
||||
<ul style={{ display: 'grid', gap: 8, ...body, paddingLeft: 18, listStyle: 'disc' }}>
|
||||
<li>Fabricate a line, an odd, or a result. Market numbers are captured, never invented.</li>
|
||||
<li>Ship a grade the model has no projection for.</li>
|
||||
<li>Render a percentage on fewer than 20 settled grades.</li>
|
||||
<li>Edit or delete a settled entry, win or miss.</li>
|
||||
<li>Take a bet. VYNDR is an analytics tool, not a sportsbook.</li>
|
||||
</ul>
|
||||
<p style={{ ...body, marginTop: 8 }}>
|
||||
The record is at <a href="/ledger" style={{ color: 'var(--text-0)' }}>vyndr.app/ledger</a>.
|
||||
Draw your own conclusion.
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -5,21 +5,25 @@ export const metadata: Metadata = {
|
||||
description: 'How VYNDR handles your data. Plain English.',
|
||||
};
|
||||
|
||||
// Session 2 (A1 board) — approval-pack draft. Entity details are placeholder
|
||||
// tokens ([ENTITY NAME], [CONTACT EMAIL]) for Kev to fill before this ships
|
||||
// as the legal document.
|
||||
|
||||
const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
{
|
||||
title: 'We never sell your personal data.',
|
||||
body: [
|
||||
'VYNDR Intelligence LLC does not sell, rent, or trade your personal data to anyone. Ever. Our business is a subscription to an analytics product — not a data resale operation.',
|
||||
'[ENTITY NAME], the company that operates VYNDR, does not sell, rent, or trade your personal data to anyone. Our business is a subscription to an analytics product — not a data resale operation.',
|
||||
],
|
||||
emphasized: true,
|
||||
},
|
||||
{
|
||||
title: 'Data we collect',
|
||||
body: [
|
||||
'Account data: email, password hash, age confirmation, signup timestamp.',
|
||||
'Account data: email, password hash (managed by Supabase Auth — we never see your plaintext password), age confirmation, signup timestamp.',
|
||||
'Usage data: reads you run (player, stat, line, sport, grade), parlays you build, page views.',
|
||||
'Payment data: Stripe processes all card data — we never see or store your card number, CVC, or any other payment instrument detail. We retain a Stripe customer ID, Stripe subscription ID, and your subscription status (active, canceled, grace period, expired) so we can gate paid features and respond to renewal/cancellation events from Stripe webhooks.',
|
||||
'Device data: IP address, browser type, basic device info (for fraud prevention and analytics).',
|
||||
'Payment data: Stripe processes all card data — we never see or store your card number, CVC, or any other payment instrument detail. We retain a Stripe customer ID, Stripe subscription ID, and your subscription status (active, canceled, grace period, expired) so we can gate paid features and respond to renewal and cancellation events from Stripe webhooks.',
|
||||
'Device data: IP address, browser type, basic device info (for fraud prevention, error monitoring, and analytics).',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -27,7 +31,7 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
body: [
|
||||
'To provide the service: sign you in, gate reads by tier, deliver grades.',
|
||||
'To improve the model: aggregate, anonymized read data helps calibration. Individual reads are never used to identify you to third parties.',
|
||||
'Aggregate trend features: "Most Read Tonight" and "Most Parlayed Tonight" use anonymized counts across all users. Your individual reads are not exposed.',
|
||||
'Aggregate trend features: surfaces like "Most Read Tonight" use anonymized counts across all users. Your individual reads are not exposed.',
|
||||
'To communicate: account confirmation, payment receipts, renewal reminders, critical service notices.',
|
||||
],
|
||||
},
|
||||
@@ -51,17 +55,18 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
title: 'Cookies and analytics',
|
||||
body: [
|
||||
'Essential cookies: auth session, read counter (cannot be disabled — the service does not work without them).',
|
||||
'Analytics: PostHog (anonymized IPs, no third-party trackers). You can opt out by setting your browser to "Do Not Track" or contacting us.',
|
||||
'Analytics: PostHog, with autocapture disabled — we record only the product events we name (page views, feature usage), never keystrokes or form contents, and no third-party ad trackers. The cookie notice on first visit is a disclosure of this. To opt out of analytics entirely, contact us at [CONTACT EMAIL].',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sub-processors',
|
||||
body: [
|
||||
'We rely on a small set of third-party processors. They handle data on our behalf under their own privacy commitments; we do not give them permission to use your data for their own purposes.',
|
||||
'Stripe — payment processing and subscription management. Receives: name (if you provide), email, billing address (if you provide), and the card details you enter on their hosted checkout page. We never see your card number.',
|
||||
'Stripe — payment processing and subscription management. Receives: name (if you provide it), email, billing address (if you provide it), and the card details you enter on their hosted checkout page. We never see your card number.',
|
||||
'Supabase — authentication, database, and file storage. Receives: everything in the "Data we collect" section above. Supabase is our primary backend.',
|
||||
'PostHog — product analytics. Receives: anonymized event data (page views, button clicks). IPs are anonymized before storage.',
|
||||
'PostHog — product analytics. Receives: the named product events above (page views, feature usage) with basic device context. Autocapture is off.',
|
||||
'Resend — transactional email (account confirmations, payment receipts, renewal reminders). Receives: your email address and the message contents.',
|
||||
'Sentry — error monitoring. Receives: technical error reports (browser type, page, stack trace), which may include your IP address in transit. Used only to find and fix bugs.',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -74,9 +79,10 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
title: 'Your rights',
|
||||
body: [
|
||||
'Access: request a copy of your data at any time.',
|
||||
'Deletion: request account and data deletion at privacy@vyndr.app — we comply within 30 days.',
|
||||
'Deletion: request account and data deletion at [CONTACT EMAIL] — we comply within 30 days.',
|
||||
'Correction: update your email and other profile data from the profile page.',
|
||||
'Export: request a JSON export of your read history.',
|
||||
'If you are in a jurisdiction with statutory privacy rights (for example the EEA, UK, or California), those rights apply to you and the requests above are how you exercise them with us.',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -94,7 +100,7 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
{
|
||||
title: 'Contact',
|
||||
body: [
|
||||
'Privacy questions or data requests: privacy@vyndr.app',
|
||||
'[ENTITY NAME]. Privacy questions or data requests: [CONTACT EMAIL].',
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -105,7 +111,7 @@ export default function PrivacyPage() {
|
||||
<header style={{ marginBottom: 32 }}>
|
||||
<h1 style={{ fontSize: 36, fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 8 }}>Privacy Policy</h1>
|
||||
<p className="mono" style={{ fontSize: 12, color: 'var(--text-tertiary)', letterSpacing: '0.05em' }}>
|
||||
EFFECTIVE: MAY 2026
|
||||
DRAFT: JULY 2026
|
||||
</p>
|
||||
</header>
|
||||
{SECTIONS.map((s) => (
|
||||
|
||||
@@ -2,70 +2,215 @@ import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Responsible Gambling',
|
||||
description: 'VYNDR provides analytics, not bets. If gambling stops being fun, stop.',
|
||||
description:
|
||||
'VYNDR is an analytics tool, not a sportsbook. 21+. If gambling stops being fun, stop. Call or text 1-800-GAMBLER.',
|
||||
};
|
||||
|
||||
// Session 2 (A1 board) — rebuilt sincerely per NORTH STAR v1.1: "Responsible
|
||||
// gaming built sincerely. The company that lasts is the one users don't
|
||||
// resent in five years." Server component. No marketing adjacency on this
|
||||
// page — no pricing links, no upsell, no product pitch.
|
||||
|
||||
const STATE_RESOURCES: { state: string; phone: string; tel: string }[] = [
|
||||
{ state: 'Arizona', phone: '1-800-NEXT-STEP', tel: '18006398783' },
|
||||
{ state: 'Colorado', phone: '1-800-522-4700', tel: '18005224700' },
|
||||
{ state: 'Illinois', phone: '1-800-GAMBLER', tel: '18004262537' },
|
||||
{ state: 'Indiana', phone: '1-800-994-8448', tel: '18009948448' },
|
||||
{ state: 'Kansas', phone: '1-800-522-4700', tel: '18005224700' },
|
||||
{ state: 'Louisiana', phone: '1-877-770-7867', tel: '18777707867' },
|
||||
{ state: 'Maryland', phone: '1-800-GAMBLER', tel: '18004262537' },
|
||||
{ state: 'Massachusetts', phone: '1-800-327-5050', tel: '18003275050' },
|
||||
{ state: 'Michigan', phone: '1-800-270-7117', tel: '18002707117' },
|
||||
{ state: 'Nevada', phone: '1-800-522-4700', tel: '18005224700' },
|
||||
{ state: 'New Jersey', phone: '1-800-GAMBLER', tel: '18004262537' },
|
||||
{ state: 'New York', phone: '1-877-846-7369', tel: '18778467369' },
|
||||
{ state: 'North Carolina', phone: '1-877-718-5543', tel: '18777185543' },
|
||||
{ state: 'Ohio', phone: '1-800-589-9966', tel: '18005899966' },
|
||||
{ state: 'Pennsylvania', phone: '1-800-GAMBLER', tel: '18004262537' },
|
||||
{ state: 'Tennessee', phone: '1-800-889-9789', tel: '18008899789' },
|
||||
{ state: 'Virginia', phone: '1-888-532-3500', tel: '18885323500' },
|
||||
];
|
||||
|
||||
const WARNING_SIGNS = [
|
||||
'Betting more than you planned, or more than you can afford to lose.',
|
||||
'Chasing losses — betting bigger to win back what you dropped.',
|
||||
'Hiding bets, or lying about them, to people you care about.',
|
||||
'Betting with money meant for rent, bills, or debts.',
|
||||
'Feeling restless or irritable when you try to cut back.',
|
||||
'Gambling to escape stress, anxiety, or a bad day.',
|
||||
'Borrowing money to bet.',
|
||||
];
|
||||
|
||||
const h2: React.CSSProperties = { fontSize: 18, fontWeight: 700, marginBottom: 12, color: 'var(--text-0)' };
|
||||
const body: React.CSSProperties = { color: 'var(--text-1)', lineHeight: 1.7, fontSize: 15 };
|
||||
|
||||
export default function ResponsibleGamblingPage() {
|
||||
return (
|
||||
<section style={{ maxWidth: 680, margin: '0 auto', padding: '48px 16px 120px' }}>
|
||||
<header style={{ marginBottom: 32 }}>
|
||||
<h1 style={{ fontSize: 36, fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 8 }}>
|
||||
If gambling stops being fun, stop.
|
||||
<header style={{ marginBottom: 28 }}>
|
||||
<h1 style={{ fontSize: 36, fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 8, color: 'var(--text-0)' }}>
|
||||
If it stops being fun, stop.
|
||||
</h1>
|
||||
<p className="mono" style={{ fontSize: 12, color: 'var(--text-tertiary)', letterSpacing: '0.05em' }}>
|
||||
RESPONSIBLE GAMBLING
|
||||
<p className="mono" style={{ fontSize: 12, color: 'var(--text-2)', letterSpacing: '0.05em' }}>
|
||||
RESPONSIBLE GAMBLING · 21+
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div style={{ color: 'var(--text-secondary)', fontSize: 16, lineHeight: 1.7 }}>
|
||||
<div style={{ ...body, fontSize: 16 }}>
|
||||
<p style={{ marginBottom: 16 }}>
|
||||
VYNDR provides sports analysis for informational purposes only. We do not take bets, facilitate wagers, or encourage gambling. We exist to help bettors make more informed decisions — including the decision to walk away.
|
||||
VYNDR is a sports data intelligence platform. We do not take bets, hold funds, or pay out
|
||||
winnings. We are not a sportsbook. You must be 21 or older to use this product, and nothing
|
||||
on it is a promise that any bet wins. Our own ledger shows the model missing, in public,
|
||||
every week.
|
||||
</p>
|
||||
<p style={{ marginBottom: 16 }}>
|
||||
Sports betting is entertainment. It should never feel like a job, a financial plan, or an escape. If you find yourself chasing losses, hiding bets from people you love, or betting more than you can afford to lose, those are warning signs that gambling has stopped being fun.
|
||||
Sports betting is entertainment with a real cost attached. It should never feel like a job,
|
||||
an income plan, or an escape. If it has started to feel like any of those, the most useful
|
||||
thing this page can do is get you to the right phone number.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="surface diagonal-cut" style={{ padding: 24, marginTop: 32, borderColor: 'var(--grade-a)' }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 12 }}>Get help</h2>
|
||||
<ul style={{ display: 'grid', gap: 12, fontSize: 15 }}>
|
||||
{/* National helpline — the load-bearing element of this page. */}
|
||||
<section
|
||||
style={{
|
||||
padding: 24,
|
||||
marginTop: 24,
|
||||
background: 'var(--bg-1)',
|
||||
border: '1px solid var(--border)',
|
||||
borderLeft: '3px solid var(--amber)',
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<h2 style={h2}>Get help now</h2>
|
||||
<p style={{ ...body, marginBottom: 14 }}>
|
||||
The National Problem Gambling Helpline. Call or text, 24/7, free, confidential. It routes
|
||||
to trained counselors in your state.
|
||||
</p>
|
||||
<a
|
||||
href="tel:18004262537"
|
||||
className="mono"
|
||||
style={{ color: 'var(--amber)', fontSize: 28, fontWeight: 700, textDecoration: 'none', letterSpacing: '0.02em' }}
|
||||
>
|
||||
1-800-GAMBLER
|
||||
</a>
|
||||
<div className="mono" style={{ fontSize: 13, color: 'var(--text-1)', marginTop: 4 }}>
|
||||
(1-800-426-2537)
|
||||
</div>
|
||||
<ul style={{ display: 'grid', gap: 10, fontSize: 14, marginTop: 18, color: 'var(--text-1)' }}>
|
||||
<li>
|
||||
<strong style={{ color: 'var(--text-primary)' }}>National Council on Problem Gambling Helpline</strong>
|
||||
<br />
|
||||
<a href="tel:18005224700" style={{ color: 'var(--grade-a)', fontSize: 24, fontWeight: 700, fontFamily: 'var(--mono)' }}>
|
||||
1-800-522-4700
|
||||
</a>
|
||||
<br />
|
||||
<span style={{ color: 'var(--text-secondary)', fontSize: 13 }}>24/7, free, confidential.</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.ncpgambling.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-primary)' }}>
|
||||
<a href="https://www.ncpgambling.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-0)' }}>
|
||||
ncpgambling.org
|
||||
</a>
|
||||
<br />
|
||||
<span style={{ color: 'var(--text-secondary)', fontSize: 13 }}>Resources, support groups, self-assessment tools.</span>
|
||||
</a>{' '}
|
||||
— National Council on Problem Gambling. Screening tools, chat, treatment directory.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.gamblersanonymous.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-primary)' }}>
|
||||
<a href="https://www.gamblersanonymous.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-0)' }}>
|
||||
gamblersanonymous.org
|
||||
</a>
|
||||
<br />
|
||||
<span style={{ color: 'var(--text-secondary)', fontSize: 13 }}>Local meetings nationwide.</span>
|
||||
</a>{' '}
|
||||
— free local meetings nationwide.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.gamtalk.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-0)' }}>
|
||||
gamtalk.org
|
||||
</a>{' '}
|
||||
— free, moderated peer support, no signup required.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section style={{ marginTop: 32 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 12 }}>Self-exclusion</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', lineHeight: 1.7, fontSize: 15 }}>
|
||||
Most US sportsbooks support a "self-exclusion" registry. You can voluntarily ban yourself from accessing sportsbook accounts for a period of months or years. Your state's gaming control board can help — search "[your state] gaming control board self-exclusion" to start.
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Warning signs</h2>
|
||||
<p style={{ ...body, marginBottom: 12 }}>
|
||||
None of these mean you are a bad person. They mean the odds have started working on you
|
||||
instead of for you.
|
||||
</p>
|
||||
<ul style={{ display: 'grid', gap: 8, ...body, paddingLeft: 18, listStyle: 'disc' }}>
|
||||
{WARNING_SIGNS.map((s) => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p style={{ ...body, marginTop: 12 }}>
|
||||
If two or more of these sound familiar, call the number above. The call costs nothing and
|
||||
commits you to nothing.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style={{ marginTop: 32 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 12 }}>Our position</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', lineHeight: 1.7, fontSize: 15 }}>
|
||||
We built VYNDR because we believe sports bettors deserve better tools than the books give them. We do not believe gambling makes anyone's life better when it stops being fun. If you need to walk away from sports betting entirely, that's the right call. Cancel your subscription, delete the app, and reach out to one of the resources above.
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>State resources</h2>
|
||||
<p style={{ ...body, marginBottom: 14 }}>
|
||||
Every state with legal sports betting funds its own problem-gambling services. If your
|
||||
state is not listed, 1-800-GAMBLER still routes you to the right place.
|
||||
</p>
|
||||
<div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
|
||||
{STATE_RESOURCES.map((r, i) => (
|
||||
<div
|
||||
key={r.state}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '10px 16px',
|
||||
borderTop: i === 0 ? 'none' : '1px solid var(--border)',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 14, color: 'var(--text-0)' }}>{r.state}</span>
|
||||
<a href={`tel:${r.tel}`} className="mono" style={{ fontSize: 13, color: 'var(--text-1)', textDecoration: 'none' }}>
|
||||
{r.phone}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Self-exclusion</h2>
|
||||
<p style={{ ...body, marginBottom: 12 }}>
|
||||
Self-exclusion is a voluntary, binding ban from sportsbooks for a period you choose —
|
||||
usually one to five years, sometimes for life. It is run by your state, not by the books,
|
||||
which is why it works.
|
||||
</p>
|
||||
<ul style={{ display: 'grid', gap: 8, ...body, paddingLeft: 18, listStyle: 'disc' }}>
|
||||
<li>
|
||||
Your state gaming board runs the registry. Search "[your state] gaming board
|
||||
self-exclusion" — in Michigan it is the Michigan Gaming Control Board's
|
||||
Responsible Gaming Database.
|
||||
</li>
|
||||
<li>
|
||||
Every licensed sportsbook also offers account-level tools: deposit limits, wager limits,
|
||||
time-outs, and permanent closure. They are in the account settings the books keep quiet.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Tools on VYNDR</h2>
|
||||
<p style={{ ...body, marginBottom: 12 }}>
|
||||
We are an analytics product, so our levers are smaller than a sportsbook's — but they
|
||||
exist, and they are yours.
|
||||
</p>
|
||||
<ul style={{ display: 'grid', gap: 8, ...body, paddingLeft: 18, listStyle: 'disc' }}>
|
||||
<li>
|
||||
<a href="/settings" style={{ color: 'var(--text-0)' }}>Settings → Responsible Play</a> —
|
||||
set a weekly time limit on your account, or step away for a while.
|
||||
</li>
|
||||
<li>
|
||||
<a href="/settings" style={{ color: 'var(--text-0)' }}>Settings → Danger Zone</a> —
|
||||
delete your account permanently. Cancellation of any paid plan takes effect immediately
|
||||
with it.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section style={{ marginTop: 36 }}>
|
||||
<h2 style={h2}>Our position</h2>
|
||||
<p style={body}>
|
||||
We publish every grade our model produces, misses included, because we think honesty is
|
||||
the only durable way to run this business. The same honesty applies here: no analytics
|
||||
tool makes gambling safe, and no grade makes a bet a sure thing. If you need to walk away
|
||||
from sports betting entirely, that is the right call and we mean it. Cancel the
|
||||
subscription, delete the account, and use the number at the top of this page. It will
|
||||
still be here.
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
+40
-18
@@ -5,70 +5,93 @@ export const metadata: Metadata = {
|
||||
description: 'VYNDR terms of service. Plain English. No tricks.',
|
||||
};
|
||||
|
||||
// Session 2 (A1 board) — approval-pack draft. Entity details are placeholder
|
||||
// tokens ([ENTITY NAME], [STATE OF FORMATION], [ARBITRATION VENUE],
|
||||
// [CONTACT EMAIL]) for Kev to fill before this ships as the legal document.
|
||||
|
||||
const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
{
|
||||
title: 'What this is',
|
||||
title: 'What this is — and is not',
|
||||
body: [
|
||||
'VYNDR provides sports analysis for informational purposes only. VYNDR does not facilitate, process, or encourage any wagers. VYNDR is not a sportsbook.',
|
||||
'VYNDR Intelligence LLC operates this product. We grade player props using statistical models, historical data, and contextual intelligence.',
|
||||
'VYNDR is a sports data intelligence platform. We publish statistical analysis, grades, and context on player props for informational and entertainment purposes only.',
|
||||
'VYNDR is not a sportsbook. We do not accept, place, facilitate, broker, or process wagers of any kind. We do not hold gambling funds, set odds for wagering, or pay out winnings. No money you send us is ever a bet.',
|
||||
'These terms are a contract between you and [ENTITY NAME], the company that operates VYNDR ("we," "us"). By creating an account or using the service you accept them.',
|
||||
],
|
||||
emphasized: true,
|
||||
},
|
||||
{
|
||||
title: 'Who can use this',
|
||||
body: [
|
||||
'You must be 21 years of age or older to create an account. You must use VYNDR only where sports betting and the use of sports analytics tools is legal in your jurisdiction. You are responsible for following your local laws.',
|
||||
'You must be 21 years of age or older to create an account. You must use VYNDR only where the use of sports analytics tools is legal in your jurisdiction, and you are responsible for knowing and following your local laws. We may close accounts that misrepresent age or location.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'No guaranteed outcomes',
|
||||
body: [
|
||||
'A grade is a probabilistic assessment. An "A" grade is not a guarantee, prediction, or promise. The model is wrong all the time — we publish the ledger so you can see how often. Past performance does not guarantee future results. Do not bet what you cannot afford to lose.',
|
||||
'A grade is a probabilistic assessment, not a prediction, promise, or guarantee. The model is wrong regularly — we publish the Ledger, misses included, so you can see exactly how often. When the model has no projection for a prop, it refuses to grade rather than guess. Past performance does not guarantee future results. Nothing on VYNDR is financial, investment, or gambling advice. Never bet money you cannot afford to lose.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Data sources and accuracy',
|
||||
body: [
|
||||
'Odds, lines, schedules, and statistics are sourced from third-party providers and public league data. They can be delayed, incomplete, or wrong, and lines move constantly. Always confirm the current line at the sportsbook before acting on anything you see here. Where our data is missing we show nothing rather than a fabricated value; absence of a number is deliberate.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Subscription terms',
|
||||
body: [
|
||||
'Paid tiers (Analyst, Desk) are billed monthly through Stripe, our payment processor. You may cancel at any time from your profile page. Cancellation takes effect at the end of the current billing period. We do not refund for partial months. If a payment fails, we honor a 48-hour grace period before reverting your account to the free tier.',
|
||||
'Founder pricing ($14.99/mo Analyst, $44.99/mo Desk) is locked for the lifetime of your continuous subscription. Lapsed subscriptions revert to standard pricing ($24.99 Analyst, $49.99 Desk) on re-subscription. After the first 100 founder seats are taken, new subscribers pay standard pricing.',
|
||||
'We may change regular pricing with 30 days notice. Founder pricing is locked.',
|
||||
'Paid tiers (Analyst, Desk) are billed monthly through Stripe, our payment processor. You may cancel at any time from your profile page; cancellation takes effect at the end of the current billing period. We do not refund partial months. If a payment fails, we honor a 48-hour grace period before reverting your account to the free tier.',
|
||||
'Founder pricing ($14.99/mo Analyst, $44.99/mo Desk) is locked for the lifetime of your continuous subscription. A lapsed subscription reverts to standard pricing ($24.99 Analyst, $49.99 Desk) on re-subscription. After the first 100 founder seats are taken, new subscribers pay standard pricing.',
|
||||
'We may change standard pricing with 30 days notice. Founder pricing is locked.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Acceptable use',
|
||||
body: [
|
||||
'Do not scrape, reverse engineer, or attempt to replicate the grading engine. Do not resell reads, share account credentials, or attempt to circumvent the read limit. Do not use the service to abuse, harass, or defraud others.',
|
||||
'VYNDR does NOT offer API access at any tier. The grading engine is consumer-only — we do not provide programmatic access to grades, factor weights, model outputs, or any other engine surface, regardless of plan or pricing. Requests for API access will be declined.',
|
||||
'Do not scrape, reverse engineer, or attempt to replicate the grading engine. Do not resell reads, share account credentials, or attempt to circumvent the read limit. Do not use the service to abuse, harass, or defraud others, and do not use it in connection with unlawful gambling.',
|
||||
'VYNDR does not offer API access at any tier. The grading engine is consumer-only — we do not provide programmatic access to grades, factor weights, model outputs, or any other engine surface, regardless of plan or pricing. Requests for API access will be declined.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Intellectual property',
|
||||
body: [
|
||||
'The grading engine, models, calibration data, factor weights, and surrounding analysis are proprietary to VYNDR Intelligence LLC. You are licensed to view grades for personal use. You are not licensed to redistribute, repackage, or sell VYNDR output.',
|
||||
'The grading engine, models, calibration data, factor weights, archetype system, and surrounding analysis are proprietary to [ENTITY NAME]. You are licensed to view grades for personal use. You are not licensed to redistribute, repackage, or sell VYNDR output.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Termination',
|
||||
body: [
|
||||
'You can delete your account at any time from Settings. We may suspend or terminate accounts that violate these terms, with notice where practical. On termination your license to use the service ends; provisions that should survive (intellectual property, liability limits, dispute terms) survive.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Limitation of liability',
|
||||
body: [
|
||||
'VYNDR is not responsible for any financial losses you incur from sports betting decisions you make using our analytics. The service is provided "as is" with no warranty. Maximum liability is limited to what you paid us in the prior 12 months.',
|
||||
'VYNDR is not responsible for any financial losses you incur from sports betting decisions, whether or not they were informed by our analytics. The service is provided "as is" and "as available," with no warranty of any kind. To the maximum extent permitted by law, our total liability is limited to the amount you paid us in the 12 months before the claim.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Governing law',
|
||||
title: 'Governing law and disputes',
|
||||
body: [
|
||||
'These terms are governed by the laws of the State of Michigan. Any disputes will be resolved through binding arbitration in Wayne County, Michigan.',
|
||||
'These terms are governed by the laws of [STATE OF FORMATION]. Disputes will be resolved through binding arbitration in [ARBITRATION VENUE], except where the law gives you a non-waivable right to court.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Responsible gambling',
|
||||
body: [
|
||||
'Sports betting can become a problem. If gambling stops being fun, stop. The National Council on Problem Gambling helpline is available 24/7, free and confidential: 1-800-522-4700. More resources at ncpgambling.org. See our Responsible Gambling page for self-exclusion guidance.',
|
||||
'Sports betting can become a problem. If it stops being fun, stop. The National Problem Gambling Helpline is available 24/7, free and confidential — call or text 1-800-GAMBLER (1-800-426-2537). Our Responsible Gambling page lists state resources, warning signs, and self-exclusion guidance.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Changes to these terms',
|
||||
body: [
|
||||
'If we make material changes, we will notify account holders by email at least 30 days before they take effect. Continued use after that date is acceptance of the new terms.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Contact',
|
||||
body: [
|
||||
'VYNDR Intelligence LLC. Questions about these terms: legal@vyndr.app',
|
||||
'[ENTITY NAME]. Questions about these terms: [CONTACT EMAIL].',
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -77,7 +100,7 @@ export default function TermsPage() {
|
||||
return (
|
||||
<LegalLayout
|
||||
title="Terms of Service"
|
||||
effective="Effective: May 2026"
|
||||
effective="Draft: July 2026"
|
||||
sections={SECTIONS}
|
||||
/>
|
||||
);
|
||||
@@ -100,7 +123,6 @@ function LegalLayout({ title, effective, sections }: { title: string; effective:
|
||||
...(s.emphasized
|
||||
? {
|
||||
borderLeft: '3px solid var(--grade-a)',
|
||||
paddingLeft: 16,
|
||||
background: 'rgba(0, 212, 160, 0.04)',
|
||||
borderRadius: 4,
|
||||
padding: '12px 16px',
|
||||
|
||||
@@ -17,6 +17,7 @@ const COLUMNS: { head: string; links: { label: string; href: string }[] }[] = [
|
||||
head: 'COMPANY',
|
||||
links: [
|
||||
{ label: 'About', href: '/about' },
|
||||
{ label: 'Methodology', href: '/methodology' },
|
||||
{ label: 'The Report', href: '/blog' },
|
||||
{ label: 'Help & FAQ', href: '/help' },
|
||||
],
|
||||
@@ -77,7 +78,7 @@ export default function Footer() {
|
||||
>
|
||||
<div style={{ ...mono, fontSize: 11, color: 'var(--text-2)' }}>
|
||||
VYNDR is an analytics tool, not a sportsbook. Not financial advice. Gamble responsibly. 21+ ·{' '}
|
||||
<a href="tel:18005224700" style={{ color: 'var(--amber)', textDecoration: 'none' }}>1-800-522-4700</a>
|
||||
<a href="tel:18004262537" style={{ color: 'var(--amber)', textDecoration: 'none' }}>1-800-GAMBLER</a>
|
||||
</div>
|
||||
<div style={{ ...mono, fontSize: 11, color: 'var(--text-2)' }}>
|
||||
BUILT BY KEVON BUTLER · DETROIT · © 2026 VYNDR
|
||||
|
||||
@@ -41,6 +41,7 @@ const OPEN_ROUTES = [
|
||||
'/responsible-gambling',
|
||||
'/help',
|
||||
'/about',
|
||||
'/methodology',
|
||||
'/terms',
|
||||
'/privacy',
|
||||
'/login',
|
||||
|
||||
Reference in New Issue
Block a user