CCCPaste

5062de766fc8a

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MEET.SPF.LAT — Private Meetings</title>
<meta name="description" content="Zero-log, end-to-end private video conferencing." />

<!-- Google Fonts: Space Mono for monospace UI elements, DM Sans for body/prose -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet" />

<style>
/* ─────────────────────────────────────────────
RESET
Normalize browser defaults across the board.
───────────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}

/* ─────────────────────────────────────────────
DESIGN TOKENS
All color, font, and spacing variables live
here. Tweak these to retheme the whole page.
───────────────────────────────────────────── */
:root {
/* Background layers */
--bg: #07111f;
--card-bg: #0c1a2e;
--card-inner-bg: #071019;

/* Brand accent (blue) */
--accent: #2496ED;
--accent-hover: #1a7fcc;
--accent-active: #1570b8;
--accent-glow: rgba(36, 150, 237, 0.35);

/* Text scale */
--text: #e8f4fd;
--text-dim: rgba(232, 244, 253, 0.55);
--text-fade: rgba(232, 244, 253, 0.35);
--text-ghost: rgba(232, 244, 253, 0.18);

/* Border scale */
--border: rgba(36, 150, 237, 0.22);
--border-soft: rgba(36, 150, 237, 0.13);
--border-ghost: rgba(36, 150, 237, 0.18);

/* Status indicator (green) */
--status-green: #1d9e75;

/* Typography */
--font-mono: 'Space Mono', monospace;
--font-sans: 'DM Sans', system-ui, sans-serif;
}

html, body {
height: 100%;
}

body {
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
font-weight: 400;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}


/* ─────────────────────────────────────────────
PAGE LAYOUT
Full-viewport flex column, centered.
All background effects are absolutely
positioned children so they don't affect flow.
───────────────────────────────────────────── */
.page {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
position: relative;
overflow: hidden;
}


/* ─────────────────────────────────────────────
BACKGROUND EFFECTS
Three layers: subtle grid, radial glows,
and floating particles. All pointer-events:none
so they never interfere with clicks.
───────────────────────────────────────────── */

/* Slowly drifting dot-grid gives depth without noise */
.bg-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(36, 150, 237, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(36, 150, 237, 0.035) 1px, transparent 1px);
background-size: 44px 44px;
pointer-events: none;
animation: gridDrift 60s linear infinite;
}

@keyframes gridDrift {
from { background-position: 0 0; }
to { background-position: 44px 44px; }
}

/* Center glow — breathes to draw attention to the card */
.bg-glow-center {
position: absolute;
width: 800px;
height: 800px;
border-radius: 50%;
background: radial-gradient(circle, rgba(36, 150, 237, 0.09) 0%, transparent 65%);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
animation: glowPulse 9s ease-in-out infinite;
}

/* Top vignette glow — softens the upper edge */
.bg-glow-top {
position: absolute;
width: 400px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(36, 150, 237, 0.06) 0%, transparent 70%);
top: -80px;
left: 50%;
transform: translateX(-50%);
pointer-events: none;
animation: glowPulse 7s ease-in-out infinite 2s;
}

@keyframes glowPulse {
0%, 100% { opacity: 0.7; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
}

/* Floating particles container */
.particles {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
}

/* Individual particles — injected by JS below */
.particle {
position: absolute;
width: 2px;
height: 2px;
border-radius: 50%;
background: var(--accent);
opacity: 0;
animation: particleRise linear infinite;
}

@keyframes particleRise {
0% { opacity: 0; transform: translateY(0) scale(0); }
10% { opacity: 0.6; }
90% { opacity: 0.15; }
100% { opacity: 0; transform: translateY(-80vh) scale(1.5); }
}


/* ─────────────────────────────────────────────
SCANLINE OVERLAY
Fixed full-viewport repeating-gradient.
Sits above everything (z-index 999) but
never blocks interaction.
───────────────────────────────────────────── */
.scanline {
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
z-index: 999;
}


/* ─────────────────────────────────────────────
CARD
The main UI container. Uses backdrop-filter
for a frosted-glass feel on the dark bg.
───────────────────────────────────────────── */
.card {
position: relative;
background: var(--card-bg);
border: 0.5px solid var(--border);
border-radius: 18px;
padding: 3rem 2.5rem 2.5rem;
width: 100%;
max-width: 490px;
box-shadow:
0 0 0 1px rgba(36, 150, 237, 0.04),
0 25px 70px rgba(0, 0, 0, 0.5),
0 0 60px rgba(36, 150, 237, 0.06);
backdrop-filter: blur(12px);
animation: cardEntrance 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes cardEntrance {
from { opacity: 0; transform: translateY(24px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}

/* Top accent bar — the glowing line along the card's top edge */
.card-accent-bar {
position: absolute;
top: 0;
left: 2.5rem;
right: 2.5rem;
height: 2px;
background: linear-gradient(
90deg,
transparent 0%,
var(--accent) 40%,
rgba(36, 150, 237, 0.4) 70%,
transparent 100%
);
border-radius: 0 0 2px 2px;
box-shadow: 0 0 12px var(--accent-glow);
}

/* Version tag — top-right corner, low contrast on purpose */
.version-label {
position: absolute;
top: 1rem;
right: 1.25rem;
font-size: 10px;
color: rgba(36, 150, 237, 0.35);
letter-spacing: 0.6px;
font-family: var(--font-mono);
}


/* ─────────────────────────────────────────────
LOGO SECTION
SVG mark + wordmark stacked vertically.
───────────────────────────────────────────── */
.logo-wrap {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 1.25rem;
gap: 8px;
}

/* Subtle vertical float on the SVG mark */
.logo-svg {
width: 130px;
height: 90px;
filter: drop-shadow(0 0 14px rgba(36, 150, 237, 0.32));
animation: logoFloat 6s ease-in-out infinite;
}

@keyframes logoFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}

/* REC indicator blink — tied to the green dot in the SVG */
.svg-rec-dot {
animation: recBlink 1.8s ease-in-out infinite;
}

@keyframes recBlink {
0%, 45%, 55%, 100% { opacity: 1; filter: drop-shadow(0 0 5px #1d9e75); }
49%, 51% { opacity: 0; filter: none; }
}

/* Lens scan line inside the camera SVG */
.svg-lens-scan {
animation: lensScan 3.5s ease-in-out infinite;
}

@keyframes lensScan {
0%, 100% { opacity: 0.15; transform: translateY(-5px); }
50% { opacity: 0.8; transform: translateY(5px); }
}

/* Wordmark under SVG */
.brand-name {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 700;
letter-spacing: 4px;
color: var(--text);
}


/* ─────────────────────────────────────────────
TAGLINE
───────────────────────────────────────────── */
.tagline {
text-align: center;
font-size: 12.5px;
color: var(--text-dim);
letter-spacing: 0.4px;
margin-bottom: 1.75rem;
font-weight: 300;
line-height: 1.5;
}

/* Accent-colored inline emphasis */
.tagline em {
color: var(--accent);
font-style: normal;
font-weight: 500;
}


/* ─────────────────────────────────────────────
SECTION DIVIDER
Full-width hairline with fade-to-transparent ends.
───────────────────────────────────────────── */
.section-divider {
height: 0.5px;
background: linear-gradient(90deg, transparent, var(--border-soft), transparent);
margin-bottom: 1.75rem;
}


/* ─────────────────────────────────────────────
CHANNEL INPUT ROW
Label + input + CTA button in a flex row.
───────────────────────────────────────────── */
.field-label {
display: block;
font-size: 10.5px;
color: var(--text-dim);
margin-bottom: 8px;
letter-spacing: 0.6px;
text-transform: uppercase;
font-weight: 500;
font-family: var(--font-mono);
}

.input-row {
display: flex;
gap: 8px;
margin-bottom: 1.25rem;
}

.room-input {
flex: 1;
background: var(--card-inner-bg);
border: 0.5px solid var(--border-ghost);
border-radius: 9px;
padding: 12px 14px;
font-size: 13.5px;
color: var(--text);
outline: none;
font-family: var(--font-mono);
font-weight: 400;
letter-spacing: 0.5px;
transition: border-color 0.2s, box-shadow 0.2s;
}

.room-input:focus {
border-color: var(--accent);
box-shadow:
0 0 0 3px rgba(36, 150, 237, 0.12),
0 0 20px rgba(36, 150, 237, 0.08);
}

.room-input::placeholder {
color: var(--text-ghost);
font-family: var(--font-mono);
letter-spacing: 0.3px;
}

/* Primary CTA button */
.btn-primary {
background: linear-gradient(135deg, var(--accent) 0%, #1a6fc4 100%);
border: none;
border-radius: 9px;
padding: 12px 18px;
font-size: 12.5px;
font-weight: 600;
color: #fff;
cursor: pointer;
font-family: var(--font-sans);
letter-spacing: 0.3px;
white-space: nowrap;
display: flex;
align-items: center;
gap: 7px;
box-shadow:
0 4px 20px rgba(36, 150, 237, 0.25),
0 0 0 1px rgba(36, 150, 237, 0.1);
position: relative;
overflow: hidden;
transition: all 0.2s;
}

/* Subtle inner highlight layer */
.btn-primary::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), transparent);
pointer-events: none;
}

.btn-primary:hover {
background: linear-gradient(135deg, #1a7fcc 0%, #1460a8 100%);
box-shadow:
0 6px 28px rgba(36, 150, 237, 0.4),
0 0 0 1px rgba(36, 150, 237, 0.2);
transform: translateY(-1px);
}

.btn-primary:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 2px 10px rgba(36, 150, 237, 0.2);
}

.btn-primary svg {
width: 13px;
height: 13px;
flex-shrink: 0;
}


/* ─────────────────────────────────────────────
PRIVACY FEATURE CHIPS
Row of small pill badges below the input.
Each communicates a privacy attribute.
───────────────────────────────────────────── */
.feature-chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 1.75rem;
}

.chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 11px;
background: rgba(36, 150, 237, 0.07);
border: 0.5px solid var(--border-soft);
border-radius: 20px;
font-size: 10.5px;
color: var(--text-dim);
letter-spacing: 0.3px;
font-weight: 500;
transition: background 0.2s, border-color 0.2s, color 0.2s;
}

.chip:hover {
background: rgba(36, 150, 237, 0.14);
border-color: rgba(36, 150, 237, 0.3);
color: var(--text);
}

/* Animated green dot for the "Zero Logs" chip */
.chip-pulse-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--status-green);
box-shadow: 0 0 6px rgba(29, 158, 117, 0.8);
animation: dotPulse 2.5s ease-in-out infinite;
}

@keyframes dotPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}

/* Small inline SVG icons inside chips */
.chip-icon {
width: 10px;
height: 10px;
stroke: var(--accent);
fill: none;
stroke-width: 1.8;
}


/* ─────────────────────────────────────────────
CARD FOOTER
Status indicator (left) + legal links (right).
───────────────────────────────────────────── */
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
padding-top: 1.25rem;
border-top: 0.5px solid var(--border-soft);
}

/* Status group — animated dot + label */
.status-group {
display: flex;
align-items: center;
gap: 6px;
}

.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-green);
box-shadow: 0 0 10px rgba(29, 158, 117, 0.7);
animation: statusPulse 2.2s ease-in-out infinite;
}

@keyframes statusPulse {
0%, 100% { opacity: 1; box-shadow: 0 0 10px rgba(29, 158, 117, 0.7); }
50% { opacity: 0.5; box-shadow: 0 0 4px rgba(29, 158, 117, 0.3); }
}

.status-label {
font-size: 11px;
color: var(--text-fade);
letter-spacing: 0.3px;
font-family: var(--font-mono);
}

/* Footer nav links */
.footer-links {
display: flex;
gap: 10px;
align-items: center;
}

.footer-separator {
color: rgba(36, 150, 237, 0.3);
font-size: 11px;
}

/* Ghost button used for Privacy/TOS toggles */
.link-btn {
font-size: 11px;
color: rgba(36, 150, 237, 0.65);
cursor: pointer;
letter-spacing: 0.3px;
background: none;
border: none;
font-family: var(--font-mono);
transition: color 0.15s;
padding: 0;
}

.link-btn:hover {
color: var(--accent);
}


/* ─────────────────────────────────────────────
EXPANDABLE INFO BOXES
Used for Privacy Policy and Terms of Service.
Toggled via .open class — JS handles the toggle.
───────────────────────────────────────────── */
.info-box {
margin-top: 1rem;
padding: 1rem;
background: var(--card-inner-bg);
border: 0.5px solid var(--border-soft);
border-radius: 9px;
font-size: 12px;
color: var(--text-dim);
line-height: 1.7;
display: none; /* collapsed by default */
animation: boxSlideIn 0.2s ease both;
}

@keyframes boxSlideIn {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}

/* JS adds/removes .open to show/hide */
.info-box.open {
display: block;
}

.info-box strong {
color: var(--accent);
font-weight: 600;
}

/* TOS-specific heading */
.tos-heading {
font-weight: 600;
color: var(--accent);
margin-bottom: 0.75rem;
font-size: 12px;
letter-spacing: 0.3px;
}

/* Scrollable TOS body — custom scrollbar for dark theme */
.tos-body {
max-height: 200px;
overflow-y: scroll;
scrollbar-width: thin;
scrollbar-color: rgba(36, 150, 237, 0.4) rgba(9, 21, 37, 0.8);
line-height: 1.75;
white-space: pre-wrap;
font-family: var(--font-sans);
font-size: 12px;
color: var(--text-dim);
}

.tos-body::-webkit-scrollbar { width: 4px; }
.tos-body::-webkit-scrollbar-track { background: rgba(9, 21, 37, 0.8); border-radius: 2px; }
.tos-body::-webkit-scrollbar-thumb { background: rgba(36, 150, 237, 0.4); border-radius: 2px; }


/* ─────────────────────────────────────────────
RESPONSIVE — MOBILE ≤ 520px
Stack the input row vertically and tighten
card padding on small screens.
───────────────────────────────────────────── */
@media (max-width: 520px) {
.card {
padding: 2.5rem 1.5rem 2rem;
}

.card-accent-bar {
left: 1.5rem;
right: 1.5rem;
}

.input-row {
flex-direction: column;
}

.btn-primary {
justify-content: center;
}
}
</style>
</head>
<body>

<!-- Scanline overlay — pure CSS texture, fixed on top of everything -->
<div class="scanline" aria-hidden="true"></div>

<!-- ── Main page wrapper ── -->
<div class="page">

<!-- Background atmosphere layers (aria-hidden, decorative) -->
<div class="bg-grid" aria-hidden="true"></div>
<div class="bg-glow-center" aria-hidden="true"></div>
<div class="bg-glow-top" aria-hidden="true"></div>
<div class="particles" id="particles" aria-hidden="true"></div>

<!-- ── Card ── -->
<div class="card" role="main">

<!-- Top glowing accent bar -->
<div class="card-accent-bar" aria-hidden="true"></div>

<!-- Version tag — cosmetic only -->
<span class="version-label" aria-hidden="true">v1.0</span>

<!-- ── Logo + Wordmark ── -->
<div class="logo-wrap">
<svg class="logo-svg" viewBox="0 0 130 90" xmlns="http://www.w3.org/2000/svg" fill="none" role="img" aria-label="MEET.SPF.LAT logo">

<!-- Camera body -->
<rect x="22" y="22" width="56" height="38" rx="7" stroke="#2496ED" stroke-width="1.5" />

<!-- Camera lens — outer ring -->
<circle cx="50" cy="41" r="13" stroke="#2496ED" stroke-width="1.2" />

<!-- Camera lens — inner fill + ring -->
<circle cx="50" cy="41" r="7.5" fill="rgba(36,150,237,0.12)" stroke="#2496ED" stroke-width="0.9" />

<!-- Animated lens scan line -->
<line class="svg-lens-scan" x1="42" y1="41" x2="58" y2="41" stroke="#2496ED" stroke-width="0.8" stroke-linecap="round" opacity="0.5" />

<!-- Lens highlight dot -->
<circle cx="47.5" cy="38.5" r="2" fill="#2496ED" opacity="0.5" />

<!-- Padlock icon (privacy indicator, top of camera body) -->
<rect x="65" y="46" width="9" height="7" rx="1.2" stroke="#2496ED" stroke-width="1" fill="rgba(36,150,237,0.12)" />
<path d="M67 46 V44.5 a2.5 2.5 0 0 1 5 0 V46" stroke="#2496ED" stroke-width="1" stroke-linecap="round" />

<!-- REC indicator dot (animated blink) -->
<circle class="svg-rec-dot" cx="69" cy="26" r="2.2" fill="#1d9e75" />
<text x="73" y="29" font-family="monospace" font-size="5" fill="rgba(29,158,117,0.7)" letter-spacing="0.5">REC</text>

<!-- Viewfinder trapezoid -->
<path d="M78 30 L96 22 L96 60 L78 52 Z" stroke="#2496ED" stroke-width="1.5" fill="rgba(36,150,237,0.07)" stroke-linejoin="round" />

<!-- Signal arcs — left side -->
<path d="M16 41 Q11 41 11 36" stroke="#2496ED" stroke-width="1" stroke-linecap="round" opacity="0.45" />
<path d="M20 41 Q13 41 13 32" stroke="#2496ED" stroke-width="1" stroke-linecap="round" opacity="0.28" />

<!-- Signal arcs — right side -->
<path d="M96 41 Q104 41 104 46" stroke="#2496ED" stroke-width="1" stroke-linecap="round" opacity="0.45" />
<path d="M96 41 Q107 41 107 52" stroke="#2496ED" stroke-width="1" stroke-linecap="round" opacity="0.28" />

<!-- Wordmark inside SVG -->
<text x="65" y="80" text-anchor="middle" font-family="Space Mono, monospace" font-size="9.5" font-weight="700" fill="#e8f4fd" letter-spacing="3.5">
MEET.SPF.LAT
</text>
</svg>
</div>

<!-- ── Tagline ── -->
<p class="tagline">
Ephemeral meetings. Nothing stored. <em>Nothing seen.</em>
</p>

<!-- Visual section break -->
<div class="section-divider" aria-hidden="true"></div>

<!-- ── Channel Name Input ── -->
<label class="field-label" for="roomName">Channel Name</label>
<div class="input-row">
<input
id="roomName"
class="room-input"
type="text"
placeholder="e.g. quiet-falcon-42"
autocomplete="off"
spellcheck="false"
aria-label="Channel name"
/>
<button class="btn-primary" onclick="openChannel()" aria-label="Open secure channel">
<!-- Lock icon -->
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="11" width="18" height="11" rx="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
Open Secure Channel
</button>
</div>

<!-- ── Privacy Feature Chips ── -->
<div class="feature-chips" role="list" aria-label="Privacy features">

<span class="chip" role="listitem">
<!-- Shield icon -->
<svg class="chip-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2 4 6v6c0 5 3.5 9 8 10 4.5-1 8-5 8-10V6l-8-4z" />
</svg>
End-to-End Encrypted
</span>

<span class="chip" role="listitem">
<span class="chip-pulse-dot" aria-hidden="true"></span>
Zero Logs
</span>

<span class="chip" role="listitem">
<!-- Clock icon -->
<svg class="chip-icon" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M12 7v5l3 2" />
</svg>
Self-Hosted
</span>

<span class="chip" role="listitem">
<!-- People icon -->
<svg class="chip-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
No Tracking
</span>

</div>

<!-- ── Footer: Status + Legal Links ── -->
<footer class="card-footer">

<div class="status-group" role="status" aria-live="polite">
<span class="status-dot" aria-hidden="true"></span>
<span class="status-label">systems nominal</span>
</div>

<nav class="footer-links" aria-label="Legal">
<button class="link-btn" onclick="togglePrivacy()" aria-expanded="false" aria-controls="privacyBox">
Privacy Policy
</button>
<span class="footer-separator" aria-hidden="true">|</span>
<button class="link-btn" onclick="toggleTOS()" aria-expanded="false" aria-controls="tosBox">
Terms of Service
</button>
</nav>

</footer>

<!-- ── Privacy Policy Expandable Box ── -->
<div id="privacyBox" class="info-box" role="region" aria-label="Privacy Policy">
<strong>Privacy Policy — No Logs, No Records</strong><br /><br />
We keep <strong>0 logs</strong> and prioritize privacy. No metadata, no chat history,
no attendance records. Rooms dissolve the moment the last participant leaves.
Nothing stored. Nothing seen. Nothing to subpoena.<br /><br />
<a
href="https://spf.lat/our_mission/"
target="_blank"
rel="noopener noreferrer"
style="color: var(--accent); text-decoration: none; font-size: 12px; letter-spacing: 0.3px;"
>
Learn more about our mission at spf.lat/our_mission/ ↗
</a>
</div>

<!-- ── Terms of Service Expandable Box ── -->
<div id="tosBox" class="info-box" role="region" aria-label="Terms of Service">
<div class="tos-heading">Terms of Service</div>
<div class="tos-body" id="tosScroll">
TERMS OF SERVICE

Effective Date: 4/2/2026

1. Acceptance of Terms

By accessing or using meet.spf.lat ("Service"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree, do not use the Service.

2. Description of Service

The Service provides browser-based and/or application-based real-time communication tools, including video, audio, and messaging functionality.

The Service is designed with a strong emphasis on user privacy and minimal data collection.

3. Eligibility

You must be at least 18 years old or the age of majority in your jurisdiction to use the Service. By using the Service, you represent and warrant that you meet this requirement.

4. Acceptable Use

You agree to use the Service only for lawful purposes and in accordance with these Terms.

You may not use the Service to:

Violate any applicable law or regulation
Engage in fraud, harassment, abuse, or threats
Distribute malware, exploit vulnerabilities, or disrupt systems
Transmit content that infringes intellectual property rights
Facilitate or participate in unlawful activities

We reserve the right to restrict or terminate access to the Service if we reasonably believe a user is violating these Terms or applicable law.

5. User Responsibility

You are solely responsible for:

All content you transmit or share through the Service
Maintaining the confidentiality of meeting links or access credentials
Ensuring your use complies with applicable laws

The Company does not monitor or control user communications and assumes no responsibility for user content.

6. Privacy and Data Practices

We prioritize user privacy and aim to minimize data collection.

We do not intentionally store or retain the contents of communications
We do not guarantee that all metadata or technical data is never generated or processed during operation
Temporary technical data may be processed as necessary to operate the Service

For more details, refer to our Privacy Policy.

7. No Expectation of Absolute Anonymity

While the Service is designed to enhance privacy, you acknowledge that:

No system can guarantee complete anonymity or security
Network providers, browsers, or third parties may collect data independently

You use the Service at your own risk.

8. Third-Party and Open Source Components

The Service may utilize third-party and open-source technologies, including but not limited to the Jitsi platform.

Such components are provided "as is" under their respective licenses
We do not control and are not responsible for third-party software behavior
Your use of the Service may be subject to those licenses where applicable

9. Intellectual Property

The Service, including its design, branding, and functionality, is owned by SPFLAT LLC and protected by applicable laws.

You retain ownership of content you create or share but grant us a limited, non-exclusive, royalty-free license to process it solely for providing the Service.

10. Service Availability

We do not guarantee that the Service will be uninterrupted, secure, or error-free.

We reserve the right to:

Modify or discontinue the Service at any time
Limit access or features without notice

11. Disclaimer of Warranties

THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE."

TO THE MAXIMUM EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, INCLUDING:

MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE
NON-INFRINGEMENT
SECURITY OR RELIABILITY

12. Limitation of Liability

TO THE MAXIMUM EXTENT PERMITTED BY LAW:

SPFLAT LLC SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING:

LOSS OF DATA
LOSS OF PROFITS
BUSINESS INTERRUPTION

OUR TOTAL LIABILITY SHALL NOT EXCEED $100 USD OR THE AMOUNT YOU PAID US (IF ANY), WHICHEVER IS GREATER.

13. Indemnification

You agree to indemnify and hold harmless SPFLAT LLC, its owners, affiliates, and agents from any claims, damages, or liabilities arising from:

Your use of the Service
Your violation of these Terms
Your violation of any law or third-party rights

14. Termination

We may suspend or terminate your access at any time, with or without notice, if:

We believe you have violated these Terms
We are required to comply with legal obligations
Your use presents risk to the Service or others

15. Compliance with Law

You agree to comply with all applicable local, state, national, and international laws.

We may cooperate with law enforcement when legally required.

16. Arbitration and Class Action Waiver (IMPORTANT ADDITION)

Any dispute arising out of or relating to these Terms or the Service shall be resolved exclusively through binding arbitration in the State of Ohio.

You agree to waive any right to:

Participate in a class action lawsuit
Have disputes resolved by a jury

This provision does not apply where prohibited by law.

17. Limitation on Claims (IMPORTANT ADDITION)

Any claim or cause of action arising out of or related to the Service must be filed within one (1) year after such claim arises, or it is permanently barred.

18. Changes to Terms

We may update these Terms at any time. Continued use of the Service after changes constitutes acceptance.

19. Governing Law

These Terms are governed by the laws of the State of Ohio, United States, without regard to conflict of law principles.

20. Severability (IMPORTANT ADDITION)

If any provision of these Terms is found to be invalid or unenforceable, the remaining provisions shall remain in full force and effect.

21. Entire Agreement (IMPORTANT ADDITION)

These Terms constitute the entire agreement between you and SPFLAT LLC regarding the Service and supersede any prior agreements.

22. Contact Information

SPFLAT LLC
Lima, OH 45807
United States

Email: [email protected]
</div>
</div>

</div><!-- /.card -->
</div><!-- /.page -->


<script>
/* ─────────────────────────────────────────────
PARTICLES
Inject 28 particles into #particles on load.
Position, size, and timing are randomised so
no two runs look identical.
───────────────────────────────────────────── */
(function spawnParticles() {
const container = document.getElementById('particles');
const COUNT = 28;

for (let i = 0; i < COUNT; i++) {
const el = document.createElement('div');
el.className = 'particle';

// Spread across full viewport width
el.style.left = Math.random() * 100 + 'vw';
el.style.bottom = '-10px';

// Vary speed so they don't all arrive at the top together
el.style.animationDuration = (12 + Math.random() * 20) + 's';

// Stagger start times so the effect feels organic from page load
el.style.animationDelay = (Math.random() * 18) + 's';

// Mix of 1–3 px for depth illusion
const size = (1 + Math.random() * 2) + 'px';
el.style.width = size;
el.style.height = size;

container.appendChild(el);
}
})();


/* ─────────────────────────────────────────────
ROOM UTILITIES
───────────────────────────────────────────── */

/**
* slugify — strip special chars and normalise a user-typed
* room name into a URL-safe lowercase slug.
*
* @param {string} value — raw input string
* @returns {string} — clean slug, e.g. "quiet-falcon-42"
*/
function slugify(value) {
return value.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // drop special chars
.replace(/\s+/g, '-') // spaces → hyphens
.replace(/-+/g, '-') // collapse consecutive hyphens
.replace(/^-|-$/g, ''); // trim leading/trailing hyphens
}

/**
* randomRoomName — generate a random adjective-noun-number slug
* used when the user leaves the input blank.
*
* @returns {string} — e.g. "silent-raven-73"
*/
function randomRoomName() {
const adjectives = [
'quiet', 'swift', 'dark', 'silent', 'hidden',
'cobalt', 'vivid', 'lunar', 'amber', 'shadow',
'hollow', 'cryptic', 'sealed', 'remote', 'cipher'
];
const nouns = [
'falcon', 'vault', 'signal', 'prism', 'echo',
'delta', 'orbit', 'fern', 'quartz', 'raven',
'mist', 'nexus', 'forge', 'drift', 'hollow'
];

const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
const noun = nouns[Math.floor(Math.random() * nouns.length)];
const num = Math.floor(Math.random() * 90 + 10); // 10–99

return `${adj}-${noun}-${num}`;
}


/* ─────────────────────────────────────────────
CHANNEL OPEN
Reads the input, slugifies it (or falls back
to a random name), then redirects to /<room>.
───────────────────────────────────────────── */
function openChannel() {
const input = document.getElementById('roomName');
let roomSlug = slugify(input.value);

// If the user left the field blank, pick a random room name
if (!roomSlug) {
roomSlug = randomRoomName();
}

// Close any open info panels before navigating
closeAllInfoBoxes();

window.location.href = '/' + encodeURIComponent(roomSlug);
}

// Allow Enter key to trigger the channel open
document.getElementById('roomName').addEventListener('keydown', function (e) {
if (e.key === 'Enter') openChannel();
});


/* ─────────────────────────────────────────────
INFO BOX TOGGLES
Privacy Policy and Terms of Service panels
are mutually exclusive — opening one closes
the other.
───────────────────────────────────────────── */

/**
* closeAllInfoBoxes — collapse both panels and reset TOS scroll.
* Called before navigation and when opening a different panel.
*/
function closeAllInfoBoxes() {
document.getElementById('privacyBox').classList.remove('open');
document.getElementById('tosBox').classList.remove('open');
document.getElementById('tosScroll').scrollTop = 0;
}

/** Toggle the Privacy Policy panel; close TOS if it was open. */
function togglePrivacy() {
const privacyBox = document.getElementById('privacyBox');
const tosBox = document.getElementById('tosBox');

// Close TOS first
tosBox.classList.remove('open');
document.getElementById('tosScroll').scrollTop = 0;

// Toggle privacy panel
privacyBox.classList.toggle('open');
}

/** Toggle the Terms of Service panel; close Privacy if it was open. */
function toggleTOS() {
const privacyBox = document.getElementById('privacyBox');
const tosBox = document.getElementById('tosBox');

// Close privacy first
privacyBox.classList.remove('open');

const wasOpen = tosBox.classList.contains('open');
tosBox.classList.toggle('open');

// Reset scroll position when opening (not when closing)
if (!wasOpen) {
document.getElementById('tosScroll').scrollTop = 0;
}
}
</script>

<script>
(function spawnParticles() {
const container = document.getElementById("particles");
for (let i = 0; i < 28; i++) {
const el = document.createElement("div");
el.className = "particle";
el.style.left = Math.random() * 100 + "vw";
el.style.bottom = "-10px";
el.style.animationDuration = (12 + Math.random() * 20) + "s";
el.style.animationDelay = (Math.random() * 18) + "s";
const size = (1 + Math.random() * 2) + "px";
el.style.width = size;
el.style.height = size;
container.appendChild(el);
}
})();

function slugify(value) {
return value.trim().toLowerCase()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}

function randomRoomName() {
const adj = ["quiet","swift","dark","silent","hidden","cobalt","vivid","lunar","amber","shadow","hollow","cryptic","sealed","remote","cipher"];
const noun = ["falcon","vault","signal","prism","echo","delta","orbit","fern","quartz","raven","mist","nexus","forge","drift","hollow"];
return adj[Math.floor(Math.random()*adj.length)] + "-" + noun[Math.floor(Math.random()*noun.length)] + "-" + Math.floor(Math.random()*90+10);
}

function openChannel() {
const input = document.getElementById("roomName");
let roomSlug = slugify(input.value);
if (!roomSlug) roomSlug = randomRoomName();
window.location.href = "/" + encodeURIComponent(roomSlug);
}

function togglePrivacy() {
const tosBox = document.getElementById("tosBox");
tosBox.classList.remove("open");
document.getElementById("tosScroll").scrollTop = 0;
document.getElementById("privacyBox").classList.toggle("open");
}

function toggleTOS() {
document.getElementById("privacyBox").classList.remove("open");
const tosBox = document.getElementById("tosBox");
const wasOpen = tosBox.classList.contains("open");
tosBox.classList.toggle("open");
if (!wasOpen) document.getElementById("tosScroll").scrollTop = 0;
}
// If we're on a room URL (e.g. /test123), init Jitsi instead of showing landing
(function checkForRoom() {
const path = window.location.pathname.replace(/^\//, '').trim();
if (path && path !== 'index.html') {
document.querySelector('.page').style.display = 'none';
const domain = window.location.hostname;
const api = new JitsiMeetExternalAPI(domain, {
roomName: path,
parentNode: document.body,
width: '100%',
height: '100%',
});
}
})();
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("openBtn").addEventListener("click", openChannel);
document.getElementById("roomName").addEventListener("keydown", function(e) {
if (e.key === "Enter") openChannel();
});
});
</script>
</body>
</html>