feat: Robotex landing on robotex.sokolab.xyz and announcement post
- projects section: standalone landing template and styles, served on a subdomain by the ingress Caddy (project) snippet - Robotex landing in RU/EN, links to Redmine forum/issues - blog post: Robotex is now a free service Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb26176277
commit
e9748e1949
8 files changed
+552
No files matched your search
@@ -0,0 +1,120 @@
|
|||||||
|
/* Project landing pages (layouts/projects/single.html). Standalone, no theme CSS. */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #0b1020;
|
||||||
|
--bg-alt: #10172b;
|
||||||
|
--surface: rgba(255, 255, 255, .04);
|
||||||
|
--border: rgba(255, 255, 255, .09);
|
||||||
|
--text: #e6e9f2;
|
||||||
|
--muted: #9aa3b8;
|
||||||
|
--accent: #7c8cff;
|
||||||
|
--accent-strong: #5b6cf0;
|
||||||
|
--code-bg: #070b16;
|
||||||
|
--radius: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: 400 1rem/1.6 'Inter', system-ui, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
a { color: var(--accent); }
|
||||||
|
code, pre { font-family: 'JetBrains Mono', monospace; }
|
||||||
|
h1, h2, h3 { letter-spacing: -.02em; line-height: 1.15; margin: 0; }
|
||||||
|
h2 { font-size: clamp(1.6rem, 3vw, 2.2rem); font-weight: 800; }
|
||||||
|
h3 { font-size: 1.05rem; font-weight: 600; }
|
||||||
|
|
||||||
|
.wrap { width: min(100% - 2rem, 1040px); margin-inline: auto; }
|
||||||
|
|
||||||
|
/* nav */
|
||||||
|
.nav {
|
||||||
|
position: sticky; top: 0; z-index: 10;
|
||||||
|
background: rgba(11, 16, 32, .8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.nav__row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; height: 3.5rem; }
|
||||||
|
.nav__logo { font-weight: 800; font-size: 1.15rem; color: var(--text); text-decoration: none; }
|
||||||
|
.nav__links { display: flex; align-items: center; gap: 1.25rem; }
|
||||||
|
.nav__links a { color: var(--muted); text-decoration: none; font-size: .9rem; }
|
||||||
|
.nav__links a:hover { color: var(--text); }
|
||||||
|
.nav__lang { padding: .15rem .5rem; border: 1px solid var(--border); border-radius: .4rem; }
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.nav__links a:not(.nav__lang) { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hero */
|
||||||
|
.hero {
|
||||||
|
padding: clamp(4rem, 12vw, 7.5rem) 0 clamp(3rem, 8vw, 5rem);
|
||||||
|
background:
|
||||||
|
radial-gradient(60rem 30rem at 50% -10%, rgba(124, 140, 255, .22), transparent 70%),
|
||||||
|
var(--bg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block; margin: 0 0 1.25rem; padding: .3rem .8rem;
|
||||||
|
border: 1px solid rgba(124, 140, 255, .4); border-radius: 999px;
|
||||||
|
color: var(--accent); font-size: .85rem; font-weight: 600;
|
||||||
|
}
|
||||||
|
.hero__title { font-size: clamp(2.2rem, 6vw, 3.6rem); font-weight: 800; max-width: 18ch; margin-inline: auto; }
|
||||||
|
.hero__lead { max-width: 40rem; margin: 1.25rem auto 0; color: var(--muted); font-size: 1.15rem; }
|
||||||
|
.hero__cta { display: flex; flex-wrap: wrap; justify-content: center; gap: .75rem; margin-top: 2rem; }
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block; padding: .75rem 1.4rem; border-radius: .7rem;
|
||||||
|
border: 1px solid var(--border); background: var(--surface);
|
||||||
|
color: var(--text); font-weight: 600; text-decoration: none;
|
||||||
|
transition: background .15s, transform .15s;
|
||||||
|
}
|
||||||
|
.btn:hover { background: rgba(255, 255, 255, .08); transform: translateY(-1px); }
|
||||||
|
.btn--primary { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; }
|
||||||
|
.btn--primary:hover { background: var(--accent); }
|
||||||
|
|
||||||
|
/* sections */
|
||||||
|
.section { padding: clamp(3rem, 8vw, 5rem) 0; scroll-margin-top: 3.5rem; }
|
||||||
|
.section--alt { background: var(--bg-alt); border-block: 1px solid var(--border); }
|
||||||
|
.section__lead { color: var(--muted); max-width: 44rem; margin: .75rem 0 0; font-size: 1.05rem; }
|
||||||
|
|
||||||
|
.problems { margin: 1.5rem 0; padding: 0; list-style: none; display: grid; gap: .6rem; counter-reset: p; }
|
||||||
|
.problems li {
|
||||||
|
counter-increment: p; display: flex; gap: .9rem; align-items: center;
|
||||||
|
padding: .8rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
|
||||||
|
}
|
||||||
|
.problems li::before {
|
||||||
|
content: counter(p); flex: none; display: grid; place-items: center;
|
||||||
|
width: 1.8rem; height: 1.8rem; border-radius: 50%;
|
||||||
|
background: rgba(124, 140, 255, .15); color: var(--accent); font-weight: 600; font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; margin-top: 2rem; }
|
||||||
|
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
|
||||||
|
.card { padding: 1.25rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
|
||||||
|
.card p { margin: .5rem 0 0; color: var(--muted); font-size: .95rem; }
|
||||||
|
.card code { font-size: .85em; color: var(--text); }
|
||||||
|
.card--link { text-decoration: none; color: var(--text); transition: border-color .15s, transform .15s; }
|
||||||
|
.card--link:hover { border-color: rgba(124, 140, 255, .5); transform: translateY(-2px); }
|
||||||
|
.card--link h3 { color: var(--accent); }
|
||||||
|
|
||||||
|
.steps { margin: 1.5rem 0 0; padding-left: 1.25rem; display: grid; gap: .4rem; }
|
||||||
|
.steps code { font-size: .9em; }
|
||||||
|
|
||||||
|
.code { display: grid; gap: 1rem; margin-top: 2rem; }
|
||||||
|
.code figure { margin: 0; min-width: 0; }
|
||||||
|
.code figcaption { color: var(--muted); font-size: .85rem; margin-bottom: .4rem; }
|
||||||
|
.code pre {
|
||||||
|
margin: 0; padding: 1rem; overflow-x: auto;
|
||||||
|
background: var(--code-bg); border: 1px solid var(--border); border-radius: var(--radius);
|
||||||
|
font-size: .82rem; line-height: 1.5; color: #c9d1e8; tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limits { margin: 1.5rem 0 0; padding-left: 1.25rem; display: grid; gap: .5rem; color: var(--muted); }
|
||||||
|
.limits strong { color: var(--text); }
|
||||||
|
|
||||||
|
/* footer */
|
||||||
|
.footer { padding: 2rem 0; border-top: 1px solid var(--border); color: var(--muted); font-size: .9rem; }
|
||||||
|
.footer__row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 2rem; }
|
||||||
|
.footer p { margin: 0; }
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: "Robotex is now a free service"
|
||||||
|
date: 2026-10-11T00:52:58+04:00
|
||||||
|
draft: false
|
||||||
|
tags:
|
||||||
|
- api
|
||||||
|
- saas
|
||||||
|
- anti-bot
|
||||||
|
- parsing
|
||||||
|
summary: "Robotex has its own page now: robotex.sokolab.xyz. It's free to use, access on request"
|
||||||
|
---
|
||||||
|
|
||||||
|
I recently [wrote](/blog/robotex-browser-automation-api/) about how Robotex works. It's my microservice that takes over all the browser work: it gets past anti-bot protection, runs a scenario on the page and hands your bot the cookies. Now it has a home of its own: **[robotex.sokolab.xyz](https://robotex.sokolab.xyz/en/)**.
|
||||||
|
|
||||||
|
## Why a separate page
|
||||||
|
|
||||||
|
A blog post is good for explaining how something works. But a project needs a place where you can quickly see what it is and why, get access and ask a question. So each of my projects will get its own subdomain with a landing page, and the blog will cover what's new.
|
||||||
|
|
||||||
|
## Free
|
||||||
|
|
||||||
|
Robotex will be a free service. You only pay for your own proxies and, if needed, captcha recognition in 2captcha.
|
||||||
|
|
||||||
|
It's an early MVP for now, so I grant access manually. Register in my [Redmine](https://redmine.sokolab.xyz/account/register), I'll approve the account and send you the API address.
|
||||||
|
|
||||||
|
## Where to discuss
|
||||||
|
|
||||||
|
The project has its own section in Redmine:
|
||||||
|
|
||||||
|
- a forum for questions, ideas and scenario discussions
|
||||||
|
- issues for bugs and feature requests
|
||||||
|
- news and plans
|
||||||
|
|
||||||
|
If it's more convenient, message me on [Telegram](https://t.me/xs0k0lx).
|
||||||
|
|
||||||
|
The source code is still open: [git.sokolab.xyz/s0k0l/robotex](https://git.sokolab.xyz/s0k0l/robotex).
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: "Robotex теперь бесплатный сервис"
|
||||||
|
date: 2026-10-11T00:52:58+04:00
|
||||||
|
draft: false
|
||||||
|
tags:
|
||||||
|
- api
|
||||||
|
- saas
|
||||||
|
- anti-bot
|
||||||
|
- parsing
|
||||||
|
summary: "У Robotex появилась своя страница: robotex.sokolab.xyz. Пользоваться можно бесплатно, доступ по заявке"
|
||||||
|
---
|
||||||
|
|
||||||
|
Недавно я [рассказывал](/blog/robotex-browser-automation-api/), как устроен Robotex, мой микросервис, который берет на себя всю работу с браузером: проходит анти-бот защиту, выполняет сценарий на странице и отдает боту куки. Теперь у него появился свой дом: **[robotex.sokolab.xyz](https://robotex.sokolab.xyz/)**.
|
||||||
|
|
||||||
|
## Зачем отдельная страница
|
||||||
|
|
||||||
|
Пост в блоге хорош, чтобы рассказать, как что-то устроено. Но проекту нужно место, где можно быстро понять, что это и зачем, получить доступ и задать вопрос. Поэтому у каждого моего проекта будет свой поддомен с лендингом, а в блоге я буду рассказывать, что нового.
|
||||||
|
|
||||||
|
## Бесплатно
|
||||||
|
|
||||||
|
Robotex будет бесплатным сервисом. Платить нужно только за свои прокси и, если понадобится, за распознавание капчи в 2captcha.
|
||||||
|
|
||||||
|
Пока это ранний MVP, поэтому доступ выдаю вручную. Зарегистрируйтесь в моем [Redmine](https://redmine.sokolab.xyz/account/register), я подтвержу аккаунт и пришлю адрес API.
|
||||||
|
|
||||||
|
## Где обсуждать
|
||||||
|
|
||||||
|
Для проекта заведен раздел в Redmine:
|
||||||
|
|
||||||
|
- форум для вопросов, идей и обсуждения сценариев
|
||||||
|
- задачи для багов и пожеланий
|
||||||
|
- новости и планы
|
||||||
|
|
||||||
|
Если удобнее, пишите мне в [Telegram](https://t.me/xs0k0lx).
|
||||||
|
|
||||||
|
Исходники по-прежнему открыты: [git.sokolab.xyz/s0k0l/robotex](https://git.sokolab.xyz/s0k0l/robotex).
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: "Projects"
|
||||||
|
build:
|
||||||
|
render: never
|
||||||
|
list: never
|
||||||
|
---
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: "Проекты"
|
||||||
|
build:
|
||||||
|
render: never
|
||||||
|
list: never
|
||||||
|
---
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
title: "Robotex"
|
||||||
|
tagline: "A browser for your bots, over an API"
|
||||||
|
description: "Robotex gets past anti-bot protection, runs a scenario on the page and hands your bot the cookies. From there the bot works with plain HTTP requests."
|
||||||
|
domain: "https://robotex.sokolab.xyz/"
|
||||||
|
badge: "Free · early access"
|
||||||
|
post: "/blog/robotex-browser-automation-api"
|
||||||
|
postLabel: "How Robotex works"
|
||||||
|
build:
|
||||||
|
list: never
|
||||||
|
sitemap:
|
||||||
|
disable: true
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- { label: "Features", href: "#features" }
|
||||||
|
- { label: "How it works", href: "#how" }
|
||||||
|
- { label: "Community", href: "#community" }
|
||||||
|
|
||||||
|
cta:
|
||||||
|
- { label: "Request access", href: "https://redmine.sokolab.xyz/account/register", primary: true }
|
||||||
|
- { label: "How it works", href: "#how" }
|
||||||
|
|
||||||
|
problem:
|
||||||
|
title: "Why you need it"
|
||||||
|
lead: "Every website bot sooner or later runs into one of three problems:"
|
||||||
|
items:
|
||||||
|
- "a captcha"
|
||||||
|
- "anti-bot protection"
|
||||||
|
- "heavy JS that has to be rendered to submit a form"
|
||||||
|
text: "A spider built on raw HTTP requests is helpless here, you need a real browser. But a browser is heavy, it has to be hidden from detection and kept next to the bot. Robotex takes that job over: the browser is used only where there's no way around it."
|
||||||
|
|
||||||
|
features:
|
||||||
|
title: "Features"
|
||||||
|
items:
|
||||||
|
- title: "Scenarios"
|
||||||
|
text: "More than opening a page. Fill a form, click, move the mouse, wait for an element, grab a piece of HTML."
|
||||||
|
- title: "Anti-bot pages"
|
||||||
|
text: "Detects a Cloudflare check before and during a scenario and gets through it. If it escalates to an image captcha, sends it to a recognition service."
|
||||||
|
- title: "Captcha"
|
||||||
|
text: "The `captcha` step solves hCaptcha and other captchas via 2captcha before the form is submitted."
|
||||||
|
- title: "Profiles"
|
||||||
|
text: "Cookies, local storage and the browser fingerprint live in one profile. Locale and timezone match the proxy's geo. To the site it's still the same user."
|
||||||
|
- title: "Real Chrome"
|
||||||
|
text: "Chrome driven by Patchright, without the CDP leaks anti-bots use to detect automation."
|
||||||
|
- title: "Any language"
|
||||||
|
text: "It's a service, not a library. Your bot only needs an HTTP client, whatever it's written in."
|
||||||
|
|
||||||
|
how:
|
||||||
|
title: "How it works"
|
||||||
|
steps:
|
||||||
|
- "The bot sends a URL, an action scenario and its proxy to `POST /v1/solve`."
|
||||||
|
- "Robotex opens the page, passes the checks and runs the scenario. The browser profile is saved and returned in the `session_id` cookie."
|
||||||
|
- "The bot gets the cookies and User-Agent and keeps browsing on its own, **through the same proxy**. When it needs a browser again, it sends `session_id` and continues in the same profile."
|
||||||
|
code:
|
||||||
|
- caption: "Request"
|
||||||
|
body: |
|
||||||
|
POST /v1/solve
|
||||||
|
X-API-Key: <key>
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://example.com/login",
|
||||||
|
"actions": [
|
||||||
|
{"type": "fill", "selector": "#email", "value": "user@example.com"},
|
||||||
|
{"type": "fill", "selector": "#password", "value": "..."},
|
||||||
|
{"type": "click", "selector": "#submit"},
|
||||||
|
{"type": "wait_for", "selector": ".dashboard", "timeout": 30}
|
||||||
|
],
|
||||||
|
"proxy": "socks5://user:pass@host:port"
|
||||||
|
}
|
||||||
|
- caption: "Response"
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
|
||||||
|
"cookies": [
|
||||||
|
{"name": "sessionid", "value": "...", "domain": ".example.com"}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{"type": "fill", "ok": true},
|
||||||
|
{"type": "fill", "ok": true},
|
||||||
|
{"type": "click", "ok": true},
|
||||||
|
{"type": "wait_for", "ok": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
limits:
|
||||||
|
title: "Where it stands, honestly"
|
||||||
|
items:
|
||||||
|
- "**It's an early MVP.** I grant access manually, and the API may change."
|
||||||
|
- "**It's free.** You only pay for your own proxies and, if needed, 2captcha."
|
||||||
|
- "Proxy quality matters a lot: no fingerprint will help on a dirty IP."
|
||||||
|
- "Turnstile doesn't pass on every site yet, I'm working on stability."
|
||||||
|
|
||||||
|
community:
|
||||||
|
title: "Community"
|
||||||
|
lead: "Questions, ideas and bugs go to Redmine. Sign-up uses manual activation: register and I'll approve your account."
|
||||||
|
links:
|
||||||
|
- { title: "Forum", text: "Questions, ideas and scenario discussions.", href: "https://redmine.sokolab.xyz/projects/robotex/boards" }
|
||||||
|
- { title: "Bugs and requests", text: "Something broke or a feature is missing.", href: "https://redmine.sokolab.xyz/projects/robotex/issues" }
|
||||||
|
- { title: "Telegram", text: "Message me directly.", href: "https://t.me/xs0k0lx" }
|
||||||
|
- { title: "Source code", text: "The service code in my Gitea.", href: "https://git.sokolab.xyz/s0k0l/robotex" }
|
||||||
|
|
||||||
|
footer: "Use Robotex only on sites where you have the right to do so."
|
||||||
|
---
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
title: "Robotex"
|
||||||
|
tagline: "Браузер для ваших ботов по API"
|
||||||
|
description: "Robotex проходит анти-бот защиту, выполняет сценарий на странице и отдает боту куки. Дальше бот работает обычными HTTP запросами."
|
||||||
|
domain: "https://robotex.sokolab.xyz/"
|
||||||
|
badge: "Бесплатно · ранний доступ"
|
||||||
|
post: "/blog/robotex-browser-automation-api"
|
||||||
|
postLabel: "Как устроен Robotex"
|
||||||
|
build:
|
||||||
|
list: never
|
||||||
|
sitemap:
|
||||||
|
disable: true
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- { label: "Возможности", href: "#features" }
|
||||||
|
- { label: "Как работает", href: "#how" }
|
||||||
|
- { label: "Сообщество", href: "#community" }
|
||||||
|
|
||||||
|
cta:
|
||||||
|
- { label: "Запросить доступ", href: "https://redmine.sokolab.xyz/account/register", primary: true }
|
||||||
|
- { label: "Как это работает", href: "#how" }
|
||||||
|
|
||||||
|
problem:
|
||||||
|
title: "Зачем это нужно"
|
||||||
|
lead: "Каждый бот под сайт рано или поздно упирается в одну из трех проблем:"
|
||||||
|
items:
|
||||||
|
- "капча"
|
||||||
|
- "анти-бот защита"
|
||||||
|
- "сложный JS, который нужно отрендерить, чтобы отправить форму"
|
||||||
|
text: "Паук на сырых HTTP запросах тут бессилен, нужен настоящий браузер. Но браузер тяжелый, его нужно прятать от детекта и держать рядом с ботом. Robotex берет эту работу на себя: браузер включается только там, где без него никак."
|
||||||
|
|
||||||
|
features:
|
||||||
|
title: "Возможности"
|
||||||
|
items:
|
||||||
|
- title: "Сценарии"
|
||||||
|
text: "Не просто открыть страницу. Заполнить форму, кликнуть, навести мышь, дождаться элемента, забрать кусок HTML."
|
||||||
|
- title: "Анти-бот страницы"
|
||||||
|
text: "Сам находит проверку Cloudflare до и во время сценария и проходит ее. Если она переросла в капчу с картинками, отправляет в сервис распознавания."
|
||||||
|
- title: "Капча"
|
||||||
|
text: "Шаг `captcha` в сценарии решает hCaptcha и другие капчи через 2captcha до отправки формы."
|
||||||
|
- title: "Профили"
|
||||||
|
text: "Куки, local storage и отпечаток браузера живут в одном профиле. Локаль и часовой пояс подбираются под гео прокси. Для сайта это все тот же пользователь."
|
||||||
|
- title: "Настоящий Chrome"
|
||||||
|
text: "Chrome под управлением Patchright, без утечек CDP, по которым анти-боты узнают автоматизацию."
|
||||||
|
- title: "Любой язык"
|
||||||
|
text: "Это сервис, а не библиотека. Боту нужен только HTTP клиент, на чем он написан, неважно."
|
||||||
|
|
||||||
|
how:
|
||||||
|
title: "Как это работает"
|
||||||
|
steps:
|
||||||
|
- "Бот отправляет в `POST /v1/solve` адрес, сценарий действий и свой прокси."
|
||||||
|
- "Robotex открывает страницу, проходит проверки и выполняет сценарий. Профиль браузера сохраняется и возвращается в куке `session_id`."
|
||||||
|
- "Бот получает куки и User-Agent и дальше ходит по сайту сам, **через тот же прокси**. Понадобится браузер снова, передаст `session_id` и продолжит в том же профиле."
|
||||||
|
code:
|
||||||
|
- caption: "Запрос"
|
||||||
|
body: |
|
||||||
|
POST /v1/solve
|
||||||
|
X-API-Key: <key>
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://example.com/login",
|
||||||
|
"actions": [
|
||||||
|
{"type": "fill", "selector": "#email", "value": "user@example.com"},
|
||||||
|
{"type": "fill", "selector": "#password", "value": "..."},
|
||||||
|
{"type": "click", "selector": "#submit"},
|
||||||
|
{"type": "wait_for", "selector": ".dashboard", "timeout": 30}
|
||||||
|
],
|
||||||
|
"proxy": "socks5://user:pass@host:port"
|
||||||
|
}
|
||||||
|
- caption: "Ответ"
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
|
||||||
|
"cookies": [
|
||||||
|
{"name": "sessionid", "value": "...", "domain": ".example.com"}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{"type": "fill", "ok": true},
|
||||||
|
{"type": "fill", "ok": true},
|
||||||
|
{"type": "click", "ok": true},
|
||||||
|
{"type": "wait_for", "ok": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
limits:
|
||||||
|
title: "Честно о текущем состоянии"
|
||||||
|
items:
|
||||||
|
- "**Это ранний MVP.** Доступ выдаю вручную, API может меняться."
|
||||||
|
- "**Бесплатно.** Платить нужно только за свои прокси и, если нужна, капчу в 2captcha."
|
||||||
|
- "Прокси решает многое: на грязном IP не поможет никакой отпечаток."
|
||||||
|
- "Turnstile проходит не на каждом сайте, над стабильностью работаю."
|
||||||
|
|
||||||
|
community:
|
||||||
|
title: "Сообщество"
|
||||||
|
lead: "Вопросы, идеи и баги принимаются в Redmine. Регистрация с ручной активацией: оставьте заявку, и я ее подтвержу."
|
||||||
|
links:
|
||||||
|
- { title: "Форум", text: "Вопросы, идеи и обсуждение сценариев.", href: "https://redmine.sokolab.xyz/projects/robotex/boards" }
|
||||||
|
- { title: "Баги и пожелания", text: "Что-то не работает или не хватает функции.", href: "https://redmine.sokolab.xyz/projects/robotex/issues" }
|
||||||
|
- { title: "Telegram", text: "Написать мне напрямую.", href: "https://t.me/xs0k0lx" }
|
||||||
|
- { title: "Исходники", text: "Код сервиса в моей Gitea.", href: "https://git.sokolab.xyz/s0k0l/robotex" }
|
||||||
|
|
||||||
|
footer: "Используйте Robotex только на сайтах, где у вас есть на это право."
|
||||||
|
---
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
{{- /*
|
||||||
|
Project landing. Served on its own subdomain (params.domain) by the ingress Caddy,
|
||||||
|
which rewrites "/" to this page and "/en/" to its English translation.
|
||||||
|
So every link here is host-relative ("/", "/en/", "#id") and assets come from
|
||||||
|
global /css/ and /img/, never from the page bundle.
|
||||||
|
*/ -}}
|
||||||
|
{{- $p := .Params -}}
|
||||||
|
{{- $home := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) -}}
|
||||||
|
{{- $css := resources.Get "css/landing.css" | minify | fingerprint -}}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ .Language.Lang }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ .Title }} · {{ $p.tagline }}</title>
|
||||||
|
<meta name="description" content="{{ .Description }}">
|
||||||
|
<link rel="canonical" href="{{ strings.TrimSuffix "/" $p.domain }}{{ $home }}">
|
||||||
|
{{- range .AllTranslations }}
|
||||||
|
{{- $h := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) }}
|
||||||
|
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ strings.TrimSuffix "/" $p.domain }}{{ $h }}">
|
||||||
|
{{- end }}
|
||||||
|
<meta property="og:title" content="{{ .Title }} · {{ $p.tagline }}">
|
||||||
|
<meta property="og:description" content="{{ .Description }}">
|
||||||
|
<meta property="og:url" content="{{ strings.TrimSuffix "/" $p.domain }}{{ $home }}">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="nav">
|
||||||
|
<div class="wrap nav__row">
|
||||||
|
<a class="nav__logo" href="{{ $home }}">{{ .Title }}</a>
|
||||||
|
<nav class="nav__links">
|
||||||
|
{{- range $p.nav }}<a href="{{ .href }}">{{ .label }}</a>{{ end }}
|
||||||
|
{{- range .Translations }}
|
||||||
|
{{- $h := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) }}
|
||||||
|
<a class="nav__lang" href="{{ $h }}" hreflang="{{ .Language.Lang }}">{{ .Language.Params.displayName }}</a>
|
||||||
|
{{- end }}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<div class="wrap">
|
||||||
|
{{ with $p.badge }}<p class="badge">{{ . }}</p>{{ end }}
|
||||||
|
<h1 class="hero__title">{{ $p.tagline }}</h1>
|
||||||
|
<p class="hero__lead">{{ .Description }}</p>
|
||||||
|
<div class="hero__cta">
|
||||||
|
{{ range $p.cta }}<a class="btn{{ if .primary }} btn--primary{{ end }}" href="{{ .href }}"{{ if hasPrefix .href "http" }} target="_blank" rel="noopener"{{ end }}>{{ .label }}</a>{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ with $p.problem }}
|
||||||
|
<section class="section" id="problem">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2>{{ .title }}</h2>
|
||||||
|
<p class="section__lead">{{ .lead }}</p>
|
||||||
|
<ol class="problems">
|
||||||
|
{{ range .items }}<li>{{ . }}</li>{{ end }}
|
||||||
|
</ol>
|
||||||
|
<p>{{ .text }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ with $p.features }}
|
||||||
|
<section class="section section--alt" id="features">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2>{{ .title }}</h2>
|
||||||
|
<div class="grid">
|
||||||
|
{{ range .items }}
|
||||||
|
<article class="card">
|
||||||
|
<h3>{{ .title }}</h3>
|
||||||
|
<p>{{ .text | markdownify }}</p>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ with $p.how }}
|
||||||
|
<section class="section" id="how">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2>{{ .title }}</h2>
|
||||||
|
<ol class="steps">
|
||||||
|
{{ range .steps }}<li>{{ . | markdownify }}</li>{{ end }}
|
||||||
|
</ol>
|
||||||
|
<div class="code">
|
||||||
|
{{ range .code }}
|
||||||
|
<figure>
|
||||||
|
<figcaption>{{ .caption }}</figcaption>
|
||||||
|
<pre><code>{{ .body }}</code></pre>
|
||||||
|
</figure>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ with $p.limits }}
|
||||||
|
<section class="section section--alt" id="limits">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2>{{ .title }}</h2>
|
||||||
|
<ul class="limits">
|
||||||
|
{{ range .items }}<li>{{ . | markdownify }}</li>{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ with $p.community }}
|
||||||
|
<section class="section" id="community">
|
||||||
|
<div class="wrap">
|
||||||
|
<h2>{{ .title }}</h2>
|
||||||
|
<p class="section__lead">{{ .lead }}</p>
|
||||||
|
<div class="grid grid--4">
|
||||||
|
{{ range .links }}
|
||||||
|
<a class="card card--link" href="{{ .href }}" target="_blank" rel="noopener">
|
||||||
|
<h3>{{ .title }} →</h3>
|
||||||
|
<p>{{ .text }}</p>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="wrap footer__row">
|
||||||
|
<p>{{ $p.footer | markdownify }}</p>
|
||||||
|
<p><a href="{{ site.Home.Permalink }}">{{ site.Params.author.name }}</a> · <a href="{{ with $p.post }}{{ (site.GetPage .).Permalink }}{{ end }}">{{ $p.postLabel }}</a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user