Анализ сайта swipe.futo.tech
Основное Готовность: 100%
Домен
swipe.futo.tech
Состояние доменного имени
?
Проверяем корректность доменного имени и наличие технических проблем на уровне домена.
Используйте для продвижения только домен второго уровня.
Отличный запоминающийся домен.
Ответ сервера
200 Успешный ответ
HTTP-код ответа и цепочка редиректов
?
Код 200 — страница доступна. Коды 3xx — редиректы (цепочки замедляют загрузку и размывают ссылочный вес). Коды 4xx/5xx — ошибки, поисковик не сможет проиндексировать страницу.
Сервер настроен корректно.
Цепочка редиректов:
http://swipe.futo.tech
301 MovedPermanently
https://swipe.futo.tech/
200 OK
Безопасность
Сайт безопасен
Использование HTTPS и SSL-сертификат
?
HTTPS — обязательный стандарт. Google и Яндекс отдают предпочтение защищённым сайтам. Отсутствие SSL или просроченный сертификат ведут к предупреждениям в браузере и снижению позиций.
Не настроен HSTS (Strict-Transport-Security) — рекомендуется включить.
На сайте работает защищенный протокол ssl и сайт открывается по https.
Ssl-сертификат действителен до 29.09.2026 18:24:03.
HTTP автоматически перенаправляется на HTTPS.
Поздравляем! Сайт не содержится в реестре РКН.
Кодировка
UTF-8
Кодировка символов страницы
?
Стандарт — UTF-8. Неправильная кодировка вызывает нечитаемые символы и мешает поисковику корректно распознать текст страницы.
Указана кодировка на странице UTF-8.
Язык
en
Атрибут lang в HTML-теге
?
Атрибут lang (<html lang="ru">) сообщает поисковикам и браузерам, на каком языке написана страница. Помогает при ранжировании в региональном поиске.
Язык документа указан явно: en.
Скорость загрузки
~0,33сек
Время отклика сервера (TTFB)
?
Time To First Byte — время до получения первого байта от сервера. Норма до 200 мс. Медленный отклик ухудшает пользовательский опыт и ранжирование: Яндекс и Google учитывают скорость страниц.
Скорость загрузки сайта 0,33сек оптимальна.
Объем документа
67Кб
Размер HTML-кода страницы
?
Слишком большой HTML замедляет парсинг браузером и сканирование поисковым роботом. Рекомендуется не более 200 Кб.
Объем html-документа 67Кб оптимален.
Структура html-документа корректна.
Ресурсы
Ресурсы: 2
Внешние ресурсы страницы (CSS, JS, изображения)
?
Количество и тип подключённых ресурсов влияют на скорость загрузки. Большое число запросов увеличивает время рендеринга страницы.
Кол-во файлов ресурсов 2 достаточно.
Показать полный список ресурсов
| Тип | Название | Значение |
|---|---|---|
| stylesheet | /fonts/fonts.css | |
| js | /tailwind.js |
Серверные заголовки
Кол-во: 11
HTTP-заголовки ответа сервера
?
Заголовки сервера передают браузеру и поисковику служебную информацию: кеширование, безопасность (CSP, HSTS), сжатие (gzip). Правильная настройка ускоряет загрузку и повышает защищённость.
Найдены серверные заголовки 11шт. Подробнее про серверные заголовки.
Показать полный список серверных заголовков
| Ключ | Значение |
|---|---|
| Date | Sat, 22 Aug 2026 18:55:23 GMT |
| X-Content-Type-Options | nosniff |
| report-to | {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Gq8CE%2F14RCcFG%2FhDTEOVJbHorZFzqSQLvrumlwLOY6PhVRgwpSlDmqxeAgyyC61UGWsrgf22jaKTyhrWKCVgN1sCOt1aBfkd0EyEqotBMqBjUP8CXQwxqPAPFYtyCanqsAo%3D"}]} |
| nel | {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800} |
| Access-Control-Allow-Origin | * |
| Cache-Control | public, must-revalidate, max-age=0 |
| Referrer-Policy | strict-origin-when-cross-origin |
| Link | </fonts/fonts.css>; rel="preload"; as=style |
| Server | cloudflare |
| cf-cache-status | DYNAMIC |
| cf-ray | a2f4176a9ba6f10a-DME |
CMS
Не определена
Система управления сайтом (движок)
?
CMS — это движок, на котором работает сайт (WordPress, 1C-Bitrix, Tilda и др.). Знание CMS помогает понять возможности SEO-оптимизации и подобрать подходящие инструменты. «Не определена» — вероятно, самописный сайт или нестандартная сборка.
CMS не определена. Вероятно, сайт самописный либо движок надёжно скрыт. Это не ошибка.
Веб-сервер
Программное обеспечение сервера
?
Веб-сервер — это ПО, которое отдаёт страницы посетителям (nginx, Apache, IIS, LiteSpeed и др.). Определяется по серверным заголовкам ответа (Server, X-Powered-By и т.п.). «Не определён» — сервер намеренно скрывает эти заголовки, это нормальная практика безопасности.
Сайт работает на веб-сервере Cloudflare.
Мета-теги Готовность: 32%
Title
FUTO Swipe
Заголовок страницы в браузере и поисковой выдаче
?
Title — главный SEO-заголовок страницы. Влияет на CTR в поиске и ранжирование. Оптимальная длина: 50–70 символов. Ключевые слова — ближе к началу.
Необходимо увеличить число символов в title (текущее значение мало: 10, минимум: 25, оптимально: от 40 до 45)
Дублей словоформ в title не найдено.
Description
Описание страницы в поисковой выдаче (сниппет)
?
Meta Description — текст под заголовком в выдаче. Напрямую на позиции не влияет, но влияет на CTR. Оптимальная длина: 120–160 символов.
Установите мета-тег description!
Keywords
Список ключевых слов страницы (устаревший тег)
?
Meta Keywords не учитывается Яндексом и Google для ранжирования с 2009–2012 годов. Заполнение не обязательно, но не вредит. Конкурент может использовать содержимое для анализа.
Установите мета-тег keywords!
Канонический Url
Указывает поисковику основную версию страницы
?
Canonical (rel=canonical) предотвращает проблему дублей страниц. Должен точно совпадать с URL проверяемой страницы. Неправильный canonical может передать ссылочный вес на другую страницу.
Рекомендуем прописать канонический Url.
Robots
Ошибок нет
Директивы для поисковых роботов на уровне страницы
?
Meta Robots управляет индексацией конкретной страницы: index/noindex — индексировать ли, follow/nofollow — следовать ли по ссылкам. Noindex полностью исключает страницу из поиска.
Meta-тег robots не указан. Страница свободна для индексации.
Адаптивность
width=device-width, initial-scale=1.0
Настройка масштабирования на мобильных устройствах
?
Тег viewport (<meta name="viewport">) сообщает браузеру, как масштабировать страницу на мобильных. Стандарт: width=device-width, initial-scale=1. Отсутствие — признак отсутствия мобильной версии.
Meta-тег viewport со значением-константой width=device-width задаёт ширину страницы в соответствии с размером экрана.
Meta-тег viewport со значением initial-scale=1.0 определяет масштаб 1:1, т.е. «не масштабировать».
Разметка OpenGraph
Не найдено
Мета-теги для красивых превью в соцсетях
?
OpenGraph (og:title, og:description, og:image) управляет тем, как страница выглядит при репосте в социальных сетях и мессенджерах. Отсутствие OG-тегов — невзрачный превью при шеринге.
Разметка OpenGraph не задана. Страница не оптимизирована под социальные сети. Мета-теги с разметкой Og помогают социальным роботам лучше структурировать Ваш сайт.
Все мета-теги
Кол-во: 1
Полный список мета-тегов страницы
?
Таблица всех meta-тегов, включая нестандартные. Позволяет найти опечатки, дубли и лишние теги.
Найдены мета-теги 1шт. Мета-теги не видимы для человека и предназначены для обмена информацией между веб-страницей и поисковыми системами, браузерами и другими веб-службами. С ними роботы 🤖 и устройства ведут себя более ожидаемо.
Показать полный список мета-тегов
| Тип | Название | Значение |
|---|---|---|
| name | viewport | width=device-width, initial-scale=1.0 |
Оптимизация Готовность: 38%
Структура
Ошибок нет
Семантические HTML-элементы страницы
?
Проверяет наличие основных структурных элементов: nav, header, footer, main. Корректная семантическая структура помогает поисковику понять архитектуру страницы.
Структура документа корректна (теги <html> и <body> присутствуют в одном экземпляре).
Контент
Есть ошибки
Объём и качество текстового содержимого
?
Анализирует объём полезного текста на странице. Слишком мало — страница может считаться малополезной. Слишком много — ухудшается читаемость и восприятие.
Слова из title 2 встречаются в тексте редко. Добавьте в контент страницы слова из тега <title>!
Кол-во слов 668 не очень много. Добавьте побольше текста (хотя бы 800 слов)!
Абзацев с текстом 23 достаточно.
Среднее число слов в абзаце 35 достаточно.
Кол-во знаков контента 4074 на странице оптимально.
Заголовки
Ошибок нет
Иерархия заголовков H1–H6
?
H1 должен быть один и содержать ключевой запрос. H2–H6 описывают подразделы. Пропуск уровней (H1 → H3) и несколько H1 — типичные ошибки, снижающие понятность страницы для поисковика.
На странице присутствуют заголовки <h2> 6. Это хорошо.
Тошнота
3,46
Насколько одно слово доминирует в тексте
?
Классическая тошнота = √(частота самого повторяющегося слова). Норма до 7–8: текст воспринимается естественно. Выше — поисковик может счесть страницу переспамленной.
Тошнота превышает норму 3. Измените текст страницы!
Академич. тошнота
24,70%
Насколько текст перенасыщен ключевыми словами
?
Академическая тошнота = (частота слова / общее количество слов) × 100%. Показывает долю конкретного слова в тексте. Норма 5–15%.
Академическая тошнота превышает норму 5-15%. Измените текст страницы!
Семантическое ядро
20
Наиболее часто встречающиеся слова на странице
?
Топ слов по частоте использования. Показывает, какие слова доминируют в тексте с точки зрения поисковика.
Контент страницы содержит осмысленный текст и слова.
Показать список слов
| Слово | Кол-во | Частота |
|---|---|---|
| models | 12 | 1,80% |
| dataset | 7 | 1,05% |
| typing | 6 | 0,90% |
| keyboard | 5 | 0,75% |
| library | 5 | 0,75% |
| million | 5 | 0,75% |
| swipes | 5 | 0,75% |
| predictions | 5 | 0,75% |
| license | 4 | 0,60% |
| huggingface | 4 | 0,60% |
| released | 3 | 0,45% |
| inference | 3 | 0,45% |
| language | 3 | 0,45% |
| training | 3 | 0,45% |
| decoder | 3 | 0,45% |
| parameters | 3 | 0,45% |
| webpage | 2 | 0,30% |
| mobile | 2 | 0,30% |
| primarily | 2 | 0,30% |
| qwerty | 2 | 0,30% |
Индексация Готовность: 30%
Индексирование
Ошибок нет
Разрешено ли индексирование страницы
?
Проверяет, не закрыта ли страница от индексации через robots.txt, meta robots или X-Robots-Tag. Страница, закрытая от индексации, не появится в поисковой выдаче.
Анкоров на странице 14 оптимально. Поисковые роботы обязательно проиндексируют сайт.
Robots.txt
Найден корректный robots.txt
Файл управления сканированием сайта роботами
?
Robots.txt указывает поисковым роботам, какие страницы сканировать, а какие — нет. Ошибки в файле могут случайно закрыть важные разделы от индексации.
Robots.txt настроен корректно. Размер файла: 69509 байт. Загружен за: 0сек.
Проверяемая страница не запрещена в robots.txt.
Robots.txt доступен по постоянному адресу
Цепочка редиректов для файла robots.txt:
http://swipe.futo.tech/robots.txt
301 MovedPermanently
https://swipe.futo.tech/robots.txt
200 OK
Показать содержимое robots.txt
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FUTO Swipe</title>
<script src="/tailwind.js"></script>
<link rel="icon" type="image/webp" href="/favicon.webp"/>
<link href="/fonts/fonts.css" rel="preload" as="style">
<link href="/fonts/fonts.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Barlow', 'sans-serif'],
mono: ['Space Grotesk', 'monospace'],
},
colors: {
brand: {
dark: '#0d1323',
accent: '#0078d4',
glow: '#2fa1cf',
surface: '#2d5080'
}
},
animation: {
'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'float': 'float 6s ease-in-out infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
}
}
}
}
}
</script>
<style>
body {
background-color: #0f172a;
color: #e2e8f0;
overflow-x: hidden;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #0f172a;
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #475569;
}
.glass-panel {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.gradient-text {
background: linear-gradient(to right, #38bdf8, #818cf8, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
canvas {
touch-action: none;
}
.pulse-1, .pulse-2, .pulse-3 {
animation: crossfade 2s ease-in-out infinite;
}
.pulse-1 {
animation-delay: 0s;
}
.pulse-2 {
animation-delay: -0.67s;
}
.pulse-3 {
animation-delay: -1.33s;
}
@keyframes crossfade {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
</style>
</head>
<body class="antialiased selection:bg-brand-accent selection:text-brand-dark">
<section class="relative min-h-screen flex flex-col items-center justify-center overflow-hidden">
<img src="hero-2.webp" alt="" loading="lazy" class="pointer-events-none absolute inset-0 h-full w-full origin-top scale-115 object-cover mix-blend-overlay">
<img src="circles.webp" alt="" loading="lazy" class="pointer-events-none absolute inset-0 h-full w-full origin-top scale-115 object-cover mix-blend-overlay">
<div class="relative z-10 text-center max-w-5xl mx-auto px-2">
<div class="mb-8">
<img src="logo.png" class="pointer-events-none selection:bg-transparent pt-4 mx-auto max-h-48 drop-shadow-[0_0_15px_rgba(255,255,255,0.3)]">
</div>
<p class="text-xl text-slate-400 max-w-2xl mx-auto mb-8 leading-relaxed">
Fast, accurate swipe typing system. Use it today in FUTO Keyboard, our fully offline Android keyboard app. Or download the models and build with it.
</p>
<div id="demo" class="relative w-full max-w-4xl mx-auto aspect-[512/360] rounded-2xl overflow-hidden shadow-2xl border border-slate-700 group">
<canvas id="keyboardCanvas" class="absolute inset-0 w-full h-full bg-slate-900 cursor-crosshair"></canvas>
</div>
<p class="text-xs text-slate-400 transition-opacity duration-1000 opacity-0 " style="position:absolute;width:100%;" id="demo-notice">This is a serverside demo to keep this webpage small. In production, it runs on-device, with much lower latency.</p>
<div class="mt-12 flex justify-center items-center gap-2 text-slate-400">
<a href="https://keyboard.futo.org/" class="px-8 py-3 bg-brand-accent text-white font-bold rounded-xl hover:bg-brand-accent/75 transition-all shadow-[0_0_20px_rgba(56,189,248,0.4)]">
Get FUTO Keyboard v0.1.29+
</a>
<a href="#foreword"><span class="ml-2 text-sm">or read more details below</span></a>
<a href="#foreword"><svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg></a>
</div>
</div>
</section>
<section id="foreword" class="px-4 py-8 bg-slate-900 relative">
<p class="md:text-xl text-slate-300 max-w-2xl mx-auto mb-8 leading-relaxed text-justify">
For a long time, good mobile swipe typing was locked behind privacy-invasive keyboard apps or unlicensed private libraries.
<br /><br />
FUTO Swipe is our family of open models and algorithms that aims to solve this problem. We developed this primarily for FUTO Keyboard, but we also welcome the broader community to make use of the FUTO Swipe models.
The models are released under a permissive license with attribution, and the inference library is open-source.
</p>
</section>
<section class="py-24 bg-brand-dark relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<header class="mb-16">
<h2 class="text-3xl md:text-4xl font-bold text-slate-100 lg:text-right ">Dataset</h2>
<div class="w-20 h-px bg-brand-accent mt-3 lg:ml-auto"></div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-5 gap-12 items-start">
<aside class="lg:col-span-2 top-24">
<blockquote class="text-2xl md:text-3xl font-semibold text-brand-accent border-l-2 border-brand-accent pl-6">
We released a dataset of 1 million swipes under the MIT license
</blockquote>
<p class="underline pt-4 text-2xl"><a href="https://huggingface.co/datasets/futo-org/swipe.futo.org">See dataset on HuggingFace</a></p>
<p class="underline pt-4 text-2xl"><a href="https://swipe.futo.org/">Contribute to our validation dataset</a></p>
</aside>
<article class="lg:col-span-3 text-slate-300 space-y-6 text-base md:text-xl leading-relaxed text-justify">
<p>
In August 2024, we launched a dataset collection effort on the swipe.futo.org domain to collect QWERTY English swipes.
Users would voluntarily visit the webpage on their mobile phone and be given instructions and information about the dataset. After consenting,
they would be given sentences, primarily from Wikipedia, and would be asked to swipe them word-by-word.
</p>
<p>
In the end, this produced over 1 million swipes. We filtered out a small set of low-quality swipes. In March 2025, we released a dataset of 1 million swipes under the MIT license, and it is available today on HuggingFace.
</p>
<p>
We made heavy use of this data to train our models and to evaluate different swipe typing systems.
</p>
</article>
</div>
</div>
</section>
<section class="py-24 bg-slate-900/50 relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-5 gap-12 items-start">
<aside class="lg:col-span-2 lg:order-last top-24">
<img src="quick-rundown.png" class="mx-auto max-w-full" style="width: 700px;">
</aside>
<article class="lg:col-span-3 text-slate-300 space-y-6 text-base md:text-xl leading-relaxed text-justify">
<header class="mb-16">
<h2 class="text-3xl md:text-4xl font-bold text-slate-100 text-left ">Models</h2>
<div class="w-20 h-px bg-brand-accent mr-auto mt-3"></div>
</header>
<p>Our architecture includes three model types.</p>
<p>The Encoder model is a universal layout-agnostic and language-agnostic, and is used for making swipe typing predictions in the general case. However, it does not offer cutting-edge accuracy.</p>
<p>The ContextLM model is a very small language model that is trained for a single language. It's used to improve the quality of predictions by eliminating nonsensical words given the preceding words in the sentence. It only requires text data for training.</p>
<p>Finally, the decoder is a language-specific and layout-specific model that learns layout's peculiarities and achieves leading accuracy. As it requires swipe typing data for a specific layout and language for training, we only have a QWERTY English decoder for now.</p>
<p>With all 3 models and with a beam width of 300, we achieve a top-4 fail rate of only ~4% on our test set. Ignoring out-of-vocabulary cases, the error rate is below 1%.</p>
</article>
</div>
</div>
</section>
<section class="py-24 bg-brand-dark relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="gap-12 items-center">
<img src="benchmark.png" class="ml-auto mr-auto">
<p class="text-center">Note: These numbers heavily depend on the benchmark, so real-world use may vary, but we believe we match big tech's keyboards.</p>
</div>
</div>
</section>
<section class="py-24 bg-slate-900/50 relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-5 gap-12 items-start">
<aside class="lg:col-span-2 top-24">
<img src="footprint.png" class="mx-auto max-w-full" style="width: 700px;">
</aside>
<article class="lg:col-span-3 text-slate-300 space-y-6 text-base md:text-xl leading-relaxed text-justify">
<header class="mb-16">
<h2 class="text-3xl md:text-4xl font-bold text-slate-100 lg:text-right ">Footprint</h2>
<div class="w-20 h-px bg-brand-accent mt-3 lg:ml-auto"></div>
</header>
<p>The encoder model is just 635,140 parameters, and the decoder is 304,155 extra. The biggest one is the ContextLM at 1.5 million, but 1.1 million of that is just embeddings. This brings us to 1,364,271 active parameters, or 2,494,767 total parameters.</p>
<p>This means the footprint of the models are very small, and the model can run on low-end devices in milliseconds. In addition, the environmental costs involved in training the models were also very low, because we never needed more than 1 workstation GPU!</p>
</article>
</div>
</div>
</section>
<section class="py-24 bg-brand-dark relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<header class="mb-16">
<h2 class="text-3xl md:text-4xl font-bold text-slate-100 text-center">C++ Library</h2>
<div class="w-20 h-px bg-brand-accent ml-auto mr-auto mt-3"></div>
</header>
<div class="grid grid-cols-1 gap-12 justify-items-center">
<article class="max-w-2xl text-slate-300 space-y-6 text-base md:text-xl leading-relaxed text-justify">
<p>The models themselves are only half of the story when going from a swipe to word predictions. The model predictions are not very useful on their own and it's necessary to perform a dictionary-constrained beam search to score a set of words and find the most likely candidates.</p>
<p>For this, we release swipe-library, a library written in C++ that handles the entire inference, decoding, and beam search part so you can easily go from swipe paths to word predictions.</p>
</article>
</div>
</div>
</section>
<section class="py-24 bg-slate-900/50 relative">
<div class="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-brand-accent to-transparent opacity-30"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<header class="mb-16">
<h2 class="text-3xl md:text-4xl font-bold text-slate-100 text-center ">Make something cool!</h2>
<div class="w-20 h-px bg-brand-accent mr-auto ml-auto mt-3"></div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
<div>
<video src="vr-swipe-small.mp4" class="rounded-2xl w-full" autoplay loop muted></video>
<p class="text-center text-xl font-bold pt-4 pb-1">Swipe typing in VR...</p>
<a href="resrec:///U-1ndAnBjY9wm/R-DD05FBF5B7486A4F8DFCA48EE94527DB314B19FCA9FF4A8B676B0EF60B42AF93" onclick="event.preventDefault(); navigator.clipboard.writeText('resrec:///U-1ndAnBjY9wm/R-DD05FBF5B7486A4F8DFCA48EE94527DB314B19FCA9FF4A8B676B0EF60B42AF93');">
<p class="text-center text-slate-500 underline">resrec link for demo</p>
</a>
</div>
<div>
<video src="trackpad-swipe.mp4" class="rounded-2xl w-full" autoplay loop muted></video>
<p class="text-center text-xl font-bold p-4">...or on a laptop trackpad</p>
</div>
</div>
</div>
</section>
<section id="download" class="py-24 bg-gradient-to-b from-slate-900 to-black">
<div class="max-w-4xl mx-auto px-4 text-center">
<h2 class="text-4xl font-bold mb-8 ">Want to build with FUTO Swipe?</h2>
<p class="text-slate-400 mb-10 text-lg">
The FUTO Swipe models are available under the FUTO Model License, and the inference library is under GPL. Read more about our architecture and method in the technical report.
</p>
<div class="mx-auto mb-4" style="width: 19.5rem;">
<a href="https://arxiv.org/abs/2606.25247" class="group relative flex items-center justify-center gap-3 p-6 rounded-xl bg-slate-800 border border-slate-700 hover:border-brand-glow transition-all hover:-translate-y-1">
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors text-2xl">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
</div>
<div class="text-left">
<div class="font-bold text-white">Technical Report</div>
</div>
</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-2xl mx-auto">
<a href="https://huggingface.co/futo-org/futo-swipe" class="group relative flex items-center justify-center gap-3 p-6 rounded-xl bg-slate-800 border border-slate-700 hover:border-brand-glow transition-all hover:-translate-y-1">
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors text-2xl">
🤗
</div>
<div class="text-left">
<div class="font-bold text-white">HuggingFace models</div>
<div class="text-xs text-slate-400"></div>
</div>
</a>
<a href="https://gitlab.futo.org/keyboard/swipe-library" class="group relative flex items-center justify-center gap-3 p-6 rounded-xl bg-slate-800 border border-slate-700 hover:border-brand-glow transition-all hover:-translate-y-1">
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
</div>
<div class="text-left">
<div class="font-bold text-white">Library</div>
<div class="text-xs text-slate-400"></div>
</div>
</a>
</div>
</div>
</section>
<footer class="bg-black py-4 border-t border-slate-700 text-center text-slate-400 text-sm">
<div class="mt-4 mb-8 flex justify-center gap-6 text-slate-400">
<a href="https://futo.tech/" class="hover:text-white transition-colors">About FUTO</a>
<a href="https://github.com/futo-org/" class="hover:text-white transition-colors">GitHub</a>
<a href="https://huggingface.co/futo-org" class="hover:text-white transition-colors">HuggingFace</a>
<a href="https://twitter.com/FUTO_Tech" class="hover:text-white transition-colors">Twitter</a>
<a href="https://chat.futo.org/" class="hover:text-white transition-colors">Zulip Chat</a>
</div>
<p> Copyright © 2026 FUTO. All rights reserved.</p>
</footer>
<script>
function dp(v, density) {
return v * (density / 160.0);
}
function parseColor(hex) {
if (!hex) return { hex: '#000000', alpha: 0 };
const clean = hex.replace('#', '');
if (clean.length === 8) {
const alpha = parseInt(clean.slice(0, 2), 16) / 255;
return { hex: `#${clean.slice(2)}`, alpha: Number(alpha.toFixed(2)) };
}
return { hex: `#${clean.slice(0, 6)}`, alpha: 1 };
}
function formatColor(hex, alpha) {
const clean = hex.replace('#', '');
const a = Math.round(alpha * 255).toString(16).padStart(2, '0').toUpperCase();
return `#${a}${clean.toUpperCase()}`;
}
function parsedToRgba(input) {
const { hex, alpha } = input;
let cleanHex = hex.startsWith('#') ? hex.slice(1) : hex;
if (cleanHex.length === 3) {
cleanHex = cleanHex
.split('')
.map(char => char + char)
.join('');
}
const r = parseInt(cleanHex.substring(0, 2), 16);
const g = parseInt(cleanHex.substring(2, 4), 16);
const b = parseInt(cleanHex.substring(4, 6), 16);
const a = Math.round(alpha * 255);
return { r, g, b, a };
}
function parseToFillColor(hex, forceAlpha) {
const col = parsedToRgba(parseColor(hex));
return `rgba(${col.r}, ${col.g}, ${col.b}, ${forceAlpha ?? (col.a / 255)})`;
}
const DEFAULT_ICONS = {
"shift_key": "/assets/shift.svg",
"delete_key": "/assets/delete.svg",
"space_key": "/assets/space.svg",
"enter_key": "/assets/enter.svg",
"action_emoji": "/assets/emoji.svg",
"chevron_right": "/assets/chevron_right.svg",
"mic_fill": "/assets/mic_fill.svg"
};
const tempCanvas = document.createElement('canvas');
function drawTintedImage(ctx, icon, x, y, w, h, tint) {
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = w;
tempCanvas.height = h;
tempCtx.drawImage(icon, 0, 0, w, h);
tempCtx.globalCompositeOperation = 'source-in';
tempCtx.fillStyle = tint;
tempCtx.fillRect(0, 0, w, h);
ctx.drawImage(tempCanvas, x, y);
}
function drawKeyForeground(ctx, currIcons, key, fgCol, hintCol, offsetY, layout) {
ctx.fillStyle = `rgba(${fgCol.r}, ${fgCol.g}, ${fgCol.b}, ${fgCol.a / 255})`;
let minSize = Math.min(key.width, key.height);
let size = minSize * 0.66; //64 * layout.density / 440;
if ((key.labelFlags & 0x4000) === 0) {
size *= 0.66;
}
if(key.label.length > 1) {
size *= 0.66;
}
let icon = matchIcon(key.iconId, currIcons);
if(key.iconId == "space_key") {
size *= 0.4;
ctx.fillStyle = `rgba(${fgCol.r}, ${fgCol.g}, ${fgCol.b}, 0.5)`;
ctx.font = `bold ${size}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText("FUTO Swipe Demo", key.drawX + key.width / 2, key.y + key.height / 2 + offsetY - size * 0.25);
size *= 0.7;
ctx.font = `bold ${size}px sans-serif`;
ctx.fillText("not a full keyboard", key.drawX + key.width / 2, key.y + key.height / 2 + offsetY + size * 1.0);
}else if (icon) {
size *= 1.1;
let cx = key.drawX + key.width / 2;
let cy = key.y + key.height / 2;
drawTintedImage(ctx, icon, cx - size / 2, cy - size / 2 + offsetY, size, size, ctx.fillStyle);
} else if (key.iconId) {
/*
size *= 0.25;
ctx.font = `bold ${size}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText(key.iconId, key.drawX + key.width / 2, key.y + key.height / 2 + offsetY);
*/
} else {
ctx.font = `bold ${size}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText(key.label, key.drawX + key.width / 2, key.y + key.height / 2 + offsetY);
}
if(key.hintLabel
// offsetY means we are rendering the key press feedback, don't need the hint on there
&& offsetY == 0
) {
size *= 0.5;
// TODO: The code for this on Android app is really convoluted and misleading
ctx.fillStyle = hintCol || `rgba(${fgCol.r}, ${fgCol.g}, ${fgCol.b}, ${(fgCol.a / 255) * 0.8})`;
ctx.font = `bold ${size}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText(key.hintLabel, key.drawX + key.width - size / 1.9, key.y + size / 1.2);
}
}
function drawFallbackBackground(ctx, key, theme, layout) {
const drawHighlight = (k, radius) => {
if(k.pressed) {
ctx.fillStyle = parseToFillColor(theme.keyboardContainerPressed);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, radius);
ctx.fill();
}
}
const byStyle = {
Action: (k, density) => {
if(!k.pressed) {
ctx.fillStyle = parseToFillColor(theme.primary);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, Math.max(k.width, k.height));
ctx.fill();
return theme.onPrimary;
} else {
ctx.fillStyle = parseToFillColor(theme.secondaryContainer);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, Math.max(k.width, k.height));
ctx.fill();
return theme.onSecondaryContainer;
}
},
Normal: (k, density) => {
const radius = dp(9, density);
if(theme.keyBorders) {
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, radius);
ctx.fill();
drawHighlight(k, radius);
return theme.onKeyboardContainer;
} else {
drawHighlight(k, radius);
return theme.onBackground;
}
},
Functional: (k, density) => {
const radius = dp(9, density);
if(theme.keyBorders) {
ctx.fillStyle = parseToFillColor(theme.keyboardContainerVariant);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, radius);
ctx.fill();
drawHighlight(k, radius);
// TODO: Add onKeyboardContainerVariant
return theme.onKeyboardContainer;
} else {
drawHighlight(k, radius);
return theme.onBackground;
}
},
Spacebar: (k, density) => {
const radius = dp(9, density);
if(theme.keyBorders) {
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, radius);
ctx.fill();
drawHighlight(k, radius);
return theme.onKeyboardContainer;
} else {
const radius = dp(100, density);
ctx.fillStyle = parseToFillColor(theme.keyboardContainerPressed);
if(!k.pressed) {
ctx.beginPath();
ctx.roundRect(k.drawX, k.y, k.width, k.height, radius);
ctx.fill();
}
return theme.onBackground;
}
},
}
const fn = byStyle[key.visualStyle];
if(fn === undefined) {
return parsedToRgba(parseColor(theme.onSurface));
} else {
return parsedToRgba(parseColor(fn(key, layout.density*layout.scale)));
}
}
function drawFallbackPopup(ctx, theme, key, layout, x, y, width, height) {
let radius = dp(9, layout.density*layout.scale);
ctx.fillStyle = parseToFillColor(theme.keyboardPress);
ctx.beginPath();
ctx.roundRect(x, y, width, height, radius);
ctx.fill();
return parsedToRgba(parseColor(theme.onKeyboardContainer));
}
function matchIcon(icon, currIcons) {
for(let candidate of currIcons) {
if(candidate[0] == icon) return candidate[1];
}
return null;
}
function renderActionBar(ctx, theme, layout, currIcons) {
if(layout.actionBar !== true) return;
let dpl = (v) => dp(v, layout.density*layout.scale);
const sep = dpl(1);
const height = layout.actionBarHeight*layout.scaleY;
ctx.fillStyle = parseColor(theme.keyboardSurfaceDim).hex;
const layoutWidth = layout.width * layout.scaleX;
// Draw background
if(theme.backgroundImage) {
ctx.fillStyle = parseToFillColor(theme.keyboardSurface, 0.5);
} else if(theme.keyBorders) {
ctx.fillStyle = parseToFillColor(theme.keyboardSurface);
} else {
ctx.fillStyle = parseToFillColor(theme.keyboardSurfaceDim);
}
ctx.fillRect(0, 0, layoutWidth, height);
//ctx.fillStyle = randomColor();
//ctx.fillRect(0, 0, dpl(42), height);
//ctx.fillStyle = randomColor();
//ctx.fillRect(layout.width - dpl(42), 0, dpl(42), height);
// Draw separators
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.fillRect(0, 0, layoutWidth, sep);
if(!theme.keyBorders) {
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.fillRect(0, height - sep, layoutWidth, sep);
}
// Draw suggestions
let words = layout.words;
for(let i=0; i<3; i++) {
const width = (layoutWidth - 2 * dpl(42)) / 3;
const maxTextWidth = width - dpl(4);
const x = dpl(42) + i*width;
//ctx.fillStyle = randomColor();
//ctx.fillRect(x, 0, width, height);
// draw text
if(words && i < words.length) {
ctx.fillStyle = parseToFillColor(i == 1 ? theme.onSurface : theme.onSurfaceVariant);
let size = dpl(18);
ctx.font = (words[i].startsWith('"') ? "italic " : "") + (i == 1 ? "bold" : "") + ` ${size}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
const metrics = ctx.measureText(words[i]);
const textWidth = metrics.width;
const scaleX = Math.min(1, maxTextWidth / textWidth);
ctx.save();
ctx.translate(x + width / 2, height / 2);
ctx.scale(scaleX, 1);
ctx.fillText(words[i], 0, 0);
ctx.restore();
}
// draw separator
if(i < 2) {
ctx.fillStyle = parseToFillColor(theme.outlineVariant);
ctx.fillRect(x + width - sep, height * 0.165, sep, height * 0.66);
}
}
// Draw buttons
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.beginPath();
ctx.arc(dpl(20), height/2, dpl(16), 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = parseToFillColor(theme.keyboardContainer);
ctx.beginPath();
ctx.arc(layoutWidth - dpl(20), height/2, dpl(16), 0, 2 * Math.PI);
ctx.fill();
const chevron = matchIcon("chevron_right", currIcons);
const mic = matchIcon("mic_fill", currIcons);
let iconSize = dpl(20);
if(chevron) drawTintedImage(ctx, chevron, dpl(20) - iconSize / 2, height / 2 - iconSize / 2, iconSize, iconSize, parseToFillColor(theme.onKeyboardContainer));
iconSize = dpl(16);
if(mic) drawTintedImage(ctx, mic, layoutWidth - dpl(20) - iconSize / 2, height / 2 - iconSize / 2, iconSize, iconSize, parseToFillColor(theme.onKeyboardContainer));
}
function modkey(key, layout) {
return {...key,
x: key.x * layout.scaleX,
drawX: key.drawX * layout.scaleX,
y: key.y * layout.scaleY,
width: key.width * layout.scaleX,
height: key.height * layout.scaleX,
};
}
function render({ctx, theme, currIcons, layout}) {
ctx.clearRect(0, 0, layout.width, layout.height);
ctx.fillStyle = parseToFillColor(theme.keyboardSurface, 1.0);
ctx.fillRect(0, 0, layout.width * layout.scaleX, layout.height * layout.scaleY);
renderActionBar(ctx, theme, layout, currIcons);
const hintCol = theme.hintCol ? parseToFillColor(theme.hintCol) : null;
for (const ak of layout.keys) {
const key = modkey(ak, layout);
let fgCol = drawFallbackBackground(ctx, key, theme, layout);
drawKeyForeground(ctx, currIcons, key, fgCol, hintCol, 0, layout);
}
for (const ak of layout.pressedKeys) {
if ((ak.actionFlags & 0x02) === 0x02) continue;
const key = modkey(ak, layout);
let fgColor = drawFallbackPopup(ctx, theme, key, layout, key.drawX, key.y - key.height, key.width, key.height * 2);
drawKeyForeground(ctx, currIcons, key, fgColor, hintCol, -key.height * 9 / 10, layout);
}
if(layout.swipe && layout.swipe.length > 0) {
const latestTime = layout.swipe[layout.swipe.length - 1].t;
const sx = layout.width * layout.scaleX;
const sy = ((layout.height - layout.actionBarFullHeight) * 3 / 4) * layout.scaleY;
const ox = 0.0;
const oy = layout.actionBarFullHeight * layout.scale;
const width = dp(8, layout.density * layout.scale);
const col = parsedToRgba(parseColor(theme.primary));
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = Math.round(layout.width * layout.scaleX);
tempCanvas.height = Math.round(layout.height * layout.scaleY);
tempCtx.lineCap = "round";
tempCtx.lineWidth = width;
for(let i=1; i<layout.swipe.length; i++) {
const {x: px, y: py, t: pt} = layout.swipe[i-1];
const {x, y, t} = layout.swipe[i];
const alpha = 1 / ((latestTime - t)/1000.0 + 1);
tempCtx.strokeStyle = `rgba(${col.r*alpha}, ${col.g*alpha}, ${col.b*alpha}, 1)`;
tempCtx.lineWidth = width*alpha;
tempCtx.beginPath();
tempCtx.moveTo(px * sx + ox, py * sy + oy);
tempCtx.lineTo(x * sx + ox, y * sy + oy);
tempCtx.stroke();
}
ctx.globalCompositeOperation = 'screen';
ctx.drawImage(tempCanvas, 0, 0);
ctx.globalCompositeOperation = 'source-over';
}
}
function enrichLayout(layout) {
if(layout.actionBar !== true) {
const actionBarHeight = dp(44, layout.density); // includes 4dp of padding
const actionBarRenderableHeight = dp(40, layout.density); // without padding
layout.actionBar = true;
layout.actionBarHeight = actionBarRenderableHeight;
layout.actionBarFullHeight = actionBarHeight;
layout.height += actionBarHeight;
layout.keys.forEach((v) => {
v.y += actionBarHeight;
})
}
if(layout.drawXCorrected !== true) {
layout.drawXCorrected = true;
layout.keys.forEach((v) => {
v.drawX = v.x + v.horizontalGap / 2;
})
}
if(layout.hitboxAdded !== true) {
layout.hitboxAdded = true;
layout.keys.forEach((v) => {
v.hitbox = [v.x, v.y, v.x + v.width + v.horizontalGap, v.y + v.height + v.verticalGap];
})
}
if(layout.pressedKeys === undefined) {
layout.pressedKeys = [];
}
}
function makeRenderContext(refresh) {
const backgrounds = [];
const icons = [];
const imageCache = {}
const styleElement = document.createElement("style");
document.head.appendChild(styleElement);
for (const [k, v] of Object.entries(DEFAULT_ICONS)) {
const img = new Image();
img.onload = () => {
imageCache[v] = img;
refresh();
}
img.src = v;
}
return { backgrounds, icons, refresh, imageCache, styleElement, registeredInput: false, theme: undefined };
}
const Fns = {};
function registerCanvasInput(rc, canvas, layout) {
let newSwipe = [];
let recognizingSwipe = false;
let pressed = false;
canvas.onpointerdown = (ev) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (ev.clientX - rect.left) * scaleX / layout.scaleX;
const y = (ev.clientY - rect.top) * scaleY / layout.scaleY;
const nx = x / layout.width;
const ny = ((y - layout.actionBarFullHeight) / (layout.height - layout.actionBarFullHeight)) * 4 / 3;
for (const key of layout.keys) {
if (key.hitbox[0] < x && key.hitbox[2] > x && key.hitbox[1] < y && key.hitbox[3] > y) {
key.pressed = true;
layout.pressedKeys.push(key);
}
}
layout.words = [];
rc.refresh();
newSwipe.length = 0;
newSwipe.push({
x: nx,
y: ny,
t: Math.round(Date.now())
});
pressed = true;
recognizingSwipe = false;
};
canvas.onpointermove = (ev) => {
if(!pressed) return;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (ev.clientX - rect.left) * scaleX / layout.scaleX;
const y = (ev.clientY - rect.top) * scaleY / layout.scaleY;
const nx = x / layout.width;
const ny = ((y - layout.actionBarFullHeight) / (layout.height - layout.actionBarFullHeight)) * 4 / 3;
newSwipe.push({
x: nx,
y: ny,
t: Math.round(Date.now())
});
if(!recognizingSwipe) {
const dx = newSwipe[0].x - newSwipe[newSwipe.length - 1].x;
const dy = newSwipe[0].y - newSwipe[newSwipe.length - 1].y;
const delta = Math.sqrt(dx * dx + dy * dy);
if(delta > 0.05) {
recognizingSwipe = true;
for (const key of layout.pressedKeys) {
key.pressed = false;
}
layout.pressedKeys.length = 0;
}
}
if(recognizingSwipe) {
layout.swipe = newSwipe;
rc.refresh();
}
}
canvas.onpointerup = (ev) => {
layout.words = [];
if(recognizingSwipe) {
layout.swipe = [...newSwipe];
Fns.runInference([...newSwipe]).then(({results, timing}) => {
console.log(results);
layout.words = [];
let idxs = [1, 0, 2];
for(let i=0; i<3; i++) {
if(idxs[i] < results.length) layout.words.push(results[idxs[i]].word); else layout.words.push("");
}
document.getElementById("demo-notice").classList.remove("opacity-0");
rc.refresh();
});
} else {
layout.swipe.length = 0;
}
recognizingSwipe = false;
for (const key of layout.pressedKeys) {
key.pressed = false;
}
layout.pressedKeys.length = 0;
pressed = false;
rc.refresh();
};
}
function updateRenderContext(rc, ctx, canvas, files, theme, layout) {
enrichLayout(layout);
rc.theme = theme;
if(0) {
canvas.width = layout.width;
canvas.height = layout.height;
layout.scaleX = 1.0;
layout.scaleY = 1.0;
layout.scale = 1.0;
} else {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
layout.scaleX = canvas.width / layout.width;
layout.scaleY = canvas.height / layout.height;
layout.scale = Math.min(layout.scaleX, layout.scaleY);
}
if(!layout.registeredInput && canvas) {
layout.registeredInput = true;
registerCanvasInput(rc, canvas, layout);
}
let density = layout.density;
rc.backgrounds.length = 0;
rc.icons.length = 0;
for (const [k, v] of Object.entries(DEFAULT_ICONS)) {
const img = rc.imageCache[v];
if(img) {
rc.icons.push([k, img]);
}
}
}
function drawRenderContext(rc, ctx, canvas, layout) {
render({ctx, currBgs: rc.backgrounds, currIcons: rc.icons, layout, theme: rc.theme, background: rc.imageCache[rc.theme.backgroundImage]});
}
</script>
<script>
const kbd = document.getElementById("keyboardCanvas");
const theme = {
id: "com.example.Theme",
name: "New Theme",
author: "",
primary: "#FFB2C8FF",
onPrimary: "#FF131D36",
primaryContainer: "#FF131D36",
onPrimaryContainer: "#FFE6E6EE",
inversePrimary: "#",
secondary: "#FFA7B3D1",
onSecondary: "#FF393B40",
secondaryContainer: "#FF17181C",
onSecondaryContainer: "#FF949499",
tertiary: "#FFADC6FF",
onTertiary: "#FF394866",
tertiaryContainer: "#FF44567A",
onTertiaryContainer: "#FFD6E3FF",
background: "#FF121316",
onBackground: "#FFE6E6EE",
surface: "#FF121316",
onSurface: "#FFE6E6EE",
onSurfaceVariant: "#FFBBBDBF",
error: "#FFFA6060",
onError: "#FF663939",
errorContainer: "#FF732323",
onErrorContainer: "#FFFFD6D6",
outline: "#FF767A8A",
outlineVariant: "#FF1D1E24",
surfaceContainerHighest: "#FF2C2F38",
keyboardSurface: "#FF121316",
keyboardSurfaceDim: "#FF0D0D0F",
keyboardContainer: "#FF1E2024",
keyboardContainerVariant: "#FF17181C",
onKeyboardContainer: "#FFE6E6EE",
keyboardPress: "#FF3C3F47",
primaryTransparent: "#4C0A1633",
onSurfaceTransparent: "#19191B21",
keyboardContainerPressed: "#54767A8A",
onKeyboardContainerPressed: "#00000000",
hintColor: null,
hintHiVis: false,
backgroundImage: "",
backgroundImageOpacity: 1.0,
backgroundImageCropping: [0, 100, 1080, 1048],
keyBorders: true,
keysFont: "",
keyIcons: [],
keyBackgrounds: []
};
const files = [];
const layout = { "density": 440, "width": 1070, "height": 656, "name": "qwerty", "keys": [{ "code": 113, "label": "q", "iconId": "", "hintLabel": "1", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 0, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 0 }, { "code": 119, "label": "w", "iconId": "", "hintLabel": "2", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 107, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 1 }, { "code": 101, "label": "e", "iconId": "", "hintLabel": "3", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 214, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 2 }, { "code": 114, "label": "r", "iconId": "", "hintLabel": "4", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 321, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 3 }, { "code": 116, "label": "t", "iconId": "", "hintLabel": "5", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 428, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 4 }, { "code": 121, "label": "y", "iconId": "", "hintLabel": "6", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 535, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 5 }, { "code": 117, "label": "u", "iconId": "", "hintLabel": "7", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 642, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 6 }, { "code": 105, "label": "i", "iconId": "", "hintLabel": "8", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 749, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 7 }, { "code": 111, "label": "o", "iconId": "", "hintLabel": "9", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 856, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 8 }, { "code": 112, "label": "p", "iconId": "", "hintLabel": "0", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 963, "y": 0, "visualStyle": "Normal", "outputText": null, "row": 0, "column": 9 }, { "code": 97, "label": "a", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 54, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 0 }, { "code": 115, "label": "s", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 161, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 1 }, { "code": 100, "label": "d", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 268, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 2 }, { "code": 102, "label": "f", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 375, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 3 }, { "code": 103, "label": "g", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 482, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 4 }, { "code": 104, "label": "h", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 589, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 5 }, { "code": 106, "label": "j", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 696, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 6 }, { "code": 107, "label": "k", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 803, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 7 }, { "code": 108, "label": "l", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 910, "y": 156, "visualStyle": "Normal", "outputText": null, "row": 1, "column": 8 }, { "code": -1, "label": "", "iconId": "shift_key", "hintLabel": "", "hintIconId": "", "labelFlags": 0, "actionFlags": 10, "width": 150, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 0, "y": 312, "visualStyle": "Functional", "outputText": null, "row": 2, "column": 0 }, { "code": 122, "label": "z", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 161, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 1 }, { "code": 120, "label": "x", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 268, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 2 }, { "code": 99, "label": "c", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 375, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 3 }, { "code": 118, "label": "v", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 482, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 4 }, { "code": 98, "label": "b", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 589, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 5 }, { "code": 110, "label": "n", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 696, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 6 }, { "code": 109, "label": "m", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 803, "y": 312, "visualStyle": "Normal", "outputText": null, "row": 2, "column": 7 }, { "code": -5, "label": "", "iconId": "delete_key", "hintLabel": "", "hintIconId": null, "labelFlags": 0, "actionFlags": 3, "width": 150, "height": 134, "horizontalGap": 11, "verticalGap": 22, "x": 910, "y": 312, "visualStyle": "Functional", "outputText": null, "row": 2, "column": 8 }, { "code": -3, "label": "?123", "iconId": "", "hintLabel": "", "hintIconId": null, "labelFlags": 0, "actionFlags": 2, "width": 150, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 0, "y": 469, "visualStyle": "Functional", "outputText": null, "row": 3, "column": 0 }, { "code": 44, "label": ",", "iconId": "", "hintLabel": "", "hintIconId": "action_settings", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 161, "y": 469, "visualStyle": "Functional", "outputText": null, "row": 3, "column": 1 }, { "code": -1050, "label": "", "iconId": "action_emoji", "hintLabel": "", "hintIconId": null, "labelFlags": 0, "actionFlags": 10, "width": 96, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 268, "y": 469, "visualStyle": "Functional", "outputText": null, "row": 3, "column": 2 }, { "code": 32, "label": "", "iconId": "space_key", "hintLabel": "", "hintIconId": null, "labelFlags": 16384, "actionFlags": 10, "width": 417, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 375, "y": 469, "visualStyle": "Spacebar", "outputText": null, "row": 3, "column": 3 }, { "code": 46, "label": ".", "iconId": "", "hintLabel": "", "hintIconId": "", "labelFlags": 16384, "actionFlags": 8, "width": 96, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 803, "y": 469, "visualStyle": "Functional", "outputText": null, "row": 3, "column": 4 }, { "code": 10, "label": "", "iconId": "enter_key", "hintLabel": " ", "hintIconId": "", "labelFlags": 0, "actionFlags": 10, "width": 150, "height": 138, "horizontalGap": 11, "verticalGap": 22, "x": 910, "y": 469, "visualStyle": "Action", "outputText": null, "row": 3, "column": 5 }], "screenWidth": 1080, "screenHeight": 2072 };
const swipe = [{"t":1730062468787,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468800,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468808,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468816,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468825,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468833,"x":0.7794841491955327,"y":0.5459787667261425},{"t":1730062468841,"x":0.768229796281757,"y":0.5303263520050413},{"t":1730062468849,"x":0.7391559009921075,"y":0.4900777461898492},{"t":1730062468858,"x":0.7222742964902461,"y":0.47442533146874794},{"t":1730062468866,"x":0.7110199435764704,"y":0.46771705137324493},{"t":1730062468875,"x":0.6997655155314969,"y":0.46100912953035056},{"t":1730062468883,"x":0.6875732247437087,"y":0.46100912953035056},{"t":1730062468891,"x":0.6725674208586745,"y":0.4587729167476467},{"t":1730062468900,"x":0.6556858163568131,"y":0.4565370622175514},{"t":1730062468908,"x":0.637866349112137,"y":0.45430084943484755},{"t":1730062468916,"x":0.620046881867461,"y":0.4498287821220484},{"t":1730062468924,"x":0.6031652773655997,"y":0.44312086027915404},{"t":1730062468933,"x":0.5862837104293372,"y":0.43417636740094717},{"t":1730062468941,"x":0.5703399686702905,"y":0.42299637824525366},{"t":1730062468950,"x":0.5572098527052866,"y":0.4118160308369515},{"t":1730062468958,"x":0.5440796991746838,"y":0.400636041681258},{"t":1730062468966,"x":0.5318874459524946,"y":0.38721948149025204},{"t":1730062468974,"x":0.5206330554731199,"y":0.3760394923345585},{"t":1730062468983,"x":0.5093786649937453,"y":0.3648591449262564},{"t":1730062468991,"x":0.5,"y":0.35367915577056286},{"t":1730062469000,"x":0.49062133500625466,"y":0.34249880836226076},{"t":1730062469008,"x":0.48124267001250937,"y":0.3335546737366625},{"t":1730062469016,"x":0.47092617984154833,"y":0.32461089736367277},{"t":1730062469024,"x":0.45967175179657477,"y":0.31566640448546596},{"t":1730062469033,"x":0.4493552240600148,"y":0.3111943371726668},{"t":1730062469041,"x":0.43810079601504126,"y":0.30448605707716386},{"t":1730062469049,"x":0.42215709182159344,"y":0.2933057096688617},{"t":1730062469058,"x":0.41090270134221885,"y":0.2888336423560626},{"t":1730062469066,"x":0.4005861736056589,"y":0.2821257205131682},{"t":1730062469075,"x":0.38933174556068534,"y":0.27765401145297763},{"t":1730062469083,"x":0.3790152178241254,"y":0.2754174404176652},{"t":1730062469091,"x":0.36963655283038005,"y":0.2709453731048661},{"t":1730062469099,"x":0.36119578814504827,"y":0.2709453731048661},{"t":1730062469108,"x":0.351817123151303,"y":0.2687095185747708},{"t":1730062469116,"x":0.34243845815755763,"y":0.26647330579206696},{"t":1730062469125,"x":0.3377491068778855,"y":0.2642374512619717},{"t":1730062469133,"x":0.33399765590662694,"y":0.2642374512619717},{"t":1730062469141,"x":0.33118406767818304,"y":0.2620012384792678},{"t":1730062469150,"x":0.3293083046269548,"y":0.2620012384792678},{"t":1730062469158,"x":0.32743257914132556,"y":0.25976538394917253},{"t":1730062469166,"x":0.3264947163985109,"y":0.25976538394917253},{"t":1730062469175,"x":0.3246189909128816,"y":0.25976538394917253},{"t":1730062469183,"x":0.32274326542725235,"y":0.25752917116646873},{"t":1730062469191,"x":0.3199296396332095,"y":0.25752917116646873},{"t":1730062469200,"x":0.3161781886619509,"y":0.25752917116646873},{"t":1730062469208,"x":0.31242673769069235,"y":0.25976538394917253},{"t":1730062469216,"x":0.3086752491538349,"y":0.25752917116646873},{"t":1730062469225,"x":0.3049237981825763,"y":0.25752917116646873},{"t":1730062469233,"x":0.3030480539141476,"y":0.25752917116646873},{"t":1730062469242,"x":0.3011723284285183,"y":0.25752917116646873},{"t":1730062469249,"x":0.30023446568570367,"y":0.25752917116646873},{"t":1730062469258,"x":0.299296602942889,"y":0.25529331663637345},{"t":1730062469266,"x":0.29835872141727493,"y":0.25752917116646873},{"t":1730062469275,"x":0.2974208586744603,"y":0.25529331663637345},{"t":1730062469283,"x":0.2964829959316456,"y":0.25529331663637345},{"t":1730062469291,"x":0.295545133188831,"y":0.2530571038536696},{"t":1730062469299,"x":0.29460727044601637,"y":0.2530571038536696},{"t":1730062469309,"x":0.29366938892040223,"y":0.2530571038536696},{"t":1730062469317,"x":0.2889800564235296,"y":0.2530571038536696},{"t":1730062469325,"x":0.2861664681950857,"y":0.2530571038536696},{"t":1730062469333,"x":0.2833528611838423,"y":0.2530571038536696},{"t":1730062469342,"x":0.2796014102125837,"y":0.2508208910709657},{"t":1730062469349,"x":0.27678780320134033,"y":0.2508208910709657},{"t":1730062469358,"x":0.27397419619009694,"y":0.24858503654087044},{"t":1730062469366,"x":0.2702227452188384,"y":0.24858503654087044},{"t":1730062469375,"x":0.2664712754647804,"y":0.24411296922807132},{"t":1730062469383,"x":0.2617819429679077,"y":0.24411296922807132},{"t":1730062469392,"x":0.25896833595666435,"y":0.24411296922807132},{"t":1730062469400,"x":0.2561547477282204,"y":0.24187675644536746},{"t":1730062469408,"x":0.25427900345979165,"y":0.24187675644536746},{"t":1730062469416,"x":0.2524032779741624,"y":0.23964090191527218},{"t":1730062469425,"x":0.2505275524885331,"y":0.23964090191527218},{"t":1730062469476,"x":0.25146541523134774,"y":0.24187675644536746},{"t":1730062469483,"x":0.2524032779741624,"y":0.24187675644536746},{"t":1730062469492,"x":0.253341140716977,"y":0.24187675644536746},{"t":1730062469500,"x":0.25427900345979165,"y":0.24411296922807132},{"t":1730062469508,"x":0.25709261047103504,"y":0.24411296922807132},{"t":1730062469516,"x":0.25990621748227843,"y":0.24634882375816658},{"t":1730062469525,"x":0.263657668453537,"y":0.24634882375816658},{"t":1730062469533,"x":0.267409138207595,"y":0.24634882375816658},{"t":1730062469541,"x":0.2749120777157111,"y":0.24187675644536746},{"t":1730062469549,"x":0.28429074270945637,"y":0.23516883460247306},{"t":1730062469558,"x":0.29460727044601637,"y":0.2262246999768748},{"t":1730062469566,"x":0.3049237981825763,"y":0.21504435256857268},{"t":1730062469574,"x":0.31711605140476556,"y":0.20386400516027056},{"t":1730062469583,"x":0.33118406767818304,"y":0.1882119486917779},{"t":1730062469591,"x":0.34712777187163085,"y":0.1770316012834758},{"t":1730062469600,"x":0.3621336508878629,"y":0.16585161212778227},{"t":1730062469608,"x":0.3790152178241254,"y":0.15690711924957543},{"t":1730062469616,"x":0.3968346850688014,"y":0.14796298462397717},{"t":1730062469625,"x":0.41277842682784815,"y":0.1390188499983789},{"t":1730062469633,"x":0.4277842682784813,"y":0.1345467826855798},{"t":1730062469641,"x":0.44372801003752804,"y":0.12783886084268536},{"t":1730062469650,"x":0.46060965210498833,"y":0.12560264805998153},{"t":1730062469658,"x":0.4756154559900226,"y":0.12560264805998153},{"t":1730062469667,"x":0.4887456095206254,"y":0.12336679352988623},{"t":1730062469675,"x":0.5018757254856293,"y":0.12113058074718239},{"t":1730062469685,"x":0.5140679787078185,"y":0.1188947262170871},{"t":1730062469692,"x":0.5271981322384214,"y":0.11665851343438326},{"t":1730062469700,"x":0.5393903854606106,"y":0.11442265890428797},{"t":1730062469708,"x":0.5506447759399852,"y":0.10995023333888028},{"t":1730062469716,"x":0.5600234409337306,"y":0.107714378808785},{"t":1730062469725,"x":0.566588517699032,"y":0.10324231149598587},{"t":1730062469733,"x":0.5750292823843637,"y":0.10324231149598587},{"t":1730062469741,"x":0.5806564964068505,"y":0.10100609871328202},{"t":1730062469749,"x":0.5862837104293372,"y":0.10324231149598587},{"t":1730062469758,"x":0.5919108868862251,"y":0.10324231149598587},{"t":1730062469766,"x":0.5937866123718544,"y":0.10324231149598587},{"t":1730062469775,"x":0.5966002381658972,"y":0.10324231149598587},{"t":1730062469783,"x":0.5984759636515264,"y":0.10324231149598587},{"t":1730062469792,"x":0.5984759636515264,"y":0.10547816602608115},{"t":1730062469800,"x":0.6003516891371558,"y":0.107714378808785},{"t":1730062469808,"x":0.6012895518799704,"y":0.10995023333888028},{"t":1730062469816,"x":0.6041031401084144,"y":0.1188947262170871},{"t":1730062469825,"x":0.6041031401084144,"y":0.12113058074718239},{"t":1730062469833,"x":0.6059789031596425,"y":0.12783886084268536},{"t":1730062469841,"x":0.6069167659024571,"y":0.1323109281554845},{"t":1730062469850,"x":0.6078546286452718,"y":0.1367833537208922},{"t":1730062469863,"x":0.6087924913880864,"y":0.1390188499983789},{"t":1730062469892,"x":0.6087924913880864,"y":0.14125506278108277},{"t":1730062469900,"x":0.6087924913880864,"y":0.14349091731117802},{"t":1730062469908,"x":0.6087924913880864,"y":0.14572713009388188},{"t":1730062469917,"x":0.609730354130901,"y":0.15019919740668103},{"t":1730062469925,"x":0.609730354130901,"y":0.15467126471948014},{"t":1730062469933,"x":0.609730354130901,"y":0.16137954481498312},{"t":1730062469941,"x":0.609730354130901,"y":0.17032367944058138},{"t":1730062469950,"x":0.609730354130901,"y":0.1770316012834758},{"t":1730062469958,"x":0.6116060796165304,"y":0.1949198705346723},{"t":1730062469967,"x":0.6125439423593451,"y":0.21504435256857268},{"t":1730062469975,"x":0.6153575681533878,"y":0.23516883460247306},{"t":1730062469983,"x":0.6172332936390171,"y":0.2620012384792678},{"t":1730062469992,"x":0.620046881867461,"y":0.2888336423560626},{"t":1730062470000,"x":0.6237983328387197,"y":0.3223743263283604},{"t":1730062470008,"x":0.6256740583243489,"y":0.358151223083362},{"t":1730062470017,"x":0.6266119210671636,"y":0.39839982889855413},{"t":1730062470025,"x":0.6266119210671636,"y":0.4408846474964502},{"t":1730062470033,"x":0.6237983328387197,"y":0.48560532062444145},{"t":1730062470042,"x":0.620046881867461,"y":0.5347984193178404},{"t":1730062470050,"x":0.6153575681533878,"y":0.5839915180112395},{"t":1730062470058,"x":0.6125439423593451,"y":0.6309484039219346},{"t":1730062470066,"x":0.6087924913880864,"y":0.6779052898326298},{"t":1730062470075,"x":0.6078546286452718,"y":0.718153895647822},{"t":1730062470083,"x":0.6087924913880864,"y":0.753930434150215},{"t":1730062470091,"x":0.6087924913880864,"y":0.7829990508097135},{"t":1730062470099,"x":0.609730354130901,"y":0.8053593873737093},{"t":1730062470108,"x":0.609730354130901,"y":0.8254838694076096},{"t":1730062470116,"x":0.6087924913880864,"y":0.838900071346007},{"t":1730062470125,"x":0.6069167659024571,"y":0.8523162732844044},{"t":1730062470133,"x":0.6041031401084144,"y":0.8634966206927065},{"t":1730062470141,"x":0.6031652773655997,"y":0.8679686880055056},{"t":1730062470150,"x":0.6012895518799704,"y":0.8746769681010086},{"t":1730062470158,"x":0.6012895518799704,"y":0.8769128226311039},{"t":1730062470166,"x":0.6003516891371558,"y":0.8769128226311039},{"t":1730062470175,"x":0.6003516891371558,"y":0.8746769681010086},{"t":1730062470183,"x":0.6012895518799704,"y":0.8746769681010086},{"t":1730062470191,"x":0.6041031401084144,"y":0.8612604079100027},{"t":1730062470199,"x":0.6069167659024571,"y":0.8433721386588061},{"t":1730062470208,"x":0.6125439423593451,"y":0.8165397347820114},{"t":1730062470216,"x":0.6153575681533878,"y":0.8031235328436139},{"t":1730062470225,"x":0.622860470095905,"y":0.7516945796201197},{"t":1730062470233,"x":0.6303634471696199,"y":0.7069735482395199},{"t":1730062470241,"x":0.6397420745977663,"y":0.6577808077987294},{"t":1730062470250,"x":0.6528722281283692,"y":0.6085877091053303},{"t":1730062470258,"x":0.6688159698874159,"y":0.5549229013517408},{"t":1730062470266,"x":0.6856974992580794,"y":0.4990218808154474},{"t":1730062470275,"x":0.7063305547311993,"y":0.4386484347137463},{"t":1730062470283,"x":0.7288394108211464,"y":0.3760394923345585},{"t":1730062470291,"x":0.750410329037081,"y":0.31566640448546596},{"t":1730062470300,"x":0.7710433845102009,"y":0.2642374512619717},{"t":1730062470308,"x":0.7879249890120623,"y":0.22175263266407566},{"t":1730062470317,"x":0.8001172046686527,"y":0.1882119486917779},{"t":1730062470325,"x":0.8076201817423676,"y":0.16585161212778227},{"t":1730062470333,"x":0.8132473581992554,"y":0.14572713009388188},{"t":1730062470341,"x":0.816998809170514,"y":0.13007471537278065},{"t":1730062470350,"x":0.819812397398958,"y":0.11665851343438326},{"t":1730062470358,"x":0.819812397398958,"y":0.10995023333888028},{"t":1730062470367,"x":0.8188745346561432,"y":0.10100609871328202},{"t":1730062470375,"x":0.8160609464276993,"y":0.0942981768703876},{"t":1730062470383,"x":0.8104337699708115,"y":0.08535404224478935},{"t":1730062470391,"x":0.801992930154282,"y":0.07640990761919109},{"t":1730062470400,"x":0.7898007144976916,"y":0.07193784030639196},{"t":1730062470408,"x":0.7729191099958302,"y":0.07417369483648724},{"t":1730062470416,"x":0.7550996427511542,"y":0.07640990761919109},{"t":1730062470425,"x":0.7335287245352197,"y":0.08982646781019706},{"t":1730062470433,"x":0.7091442180908412,"y":0.107714378808785},{"t":1730062470442,"x":0.6828839110296355,"y":0.1323109281554845},{"t":1730062470450,"x":0.6556858163568131,"y":0.16137954481498312},{"t":1730062470458,"x":0.6256740583243489,"y":0.19044780322187319},{"t":1730062470466,"x":0.594724475114669,"y":0.2306971255422825},{"t":1730062470475,"x":0.5618991664193598,"y":0.2709453731048661},{"t":1730062470483,"x":0.528135994981236,"y":0.31566640448546596},{"t":1730062470492,"x":0.4803048072696947,"y":0.3805115596473576},{"t":1730062470500,"x":0.46342320276783333,"y":0.400636041681258},{"t":1730062470508,"x":0.43247361955815344,"y":0.4386484347137463},{"t":1730062470517,"x":0.38933174556068534,"y":0.4811332533116423},{"t":1730062470525,"x":0.3762016295956815,"y":0.4900777461898492},{"t":1730062470534,"x":0.33962483236351476,"y":0.5146739372839401},{"t":1730062470542,"x":0.3293083046269548,"y":0.5213822173794431},{"t":1730062470550,"x":0.3030480539141476,"y":0.5347984193178404},{"t":1730062470558,"x":0.28804219368071493,"y":0.5437425539434387},{"t":1730062470567,"x":0.2720984707044677,"y":0.552686688569037},{"t":1730062470575,"x":0.25709261047103504,"y":0.5661028905074343},{"t":1730062470583,"x":0.24396247572323168,"y":0.5795190924458318},{"t":1730062470592,"x":0.2308323597582278,"y":0.5929356526368377},{"t":1730062470600,"x":0.21582649952479518,"y":0.6085877091053303},{"t":1730062470608,"x":0.2017585020341772,"y":0.6220039110437278},{"t":1730062470616,"x":0.18956623002918846,"y":0.6309484039219346},{"t":1730062470625,"x":0.17737397680699923,"y":0.637656325764829},{"t":1730062470633,"x":0.16893317455606854,"y":0.6421283930776281},{"t":1730062470642,"x":0.16143023504795248,"y":0.6421283930776281},{"t":1730062470650,"x":0.15674090255107984,"y":0.6421283930776281},{"t":1730062470658,"x":0.1539273143226359,"y":0.6421283930776281},{"t":1730062470666,"x":0.14923798182576323,"y":0.637656325764829},{"t":1730062470675,"x":0.14642438420591958,"y":0.6331842584520299},{"t":1730062470683,"x":0.14454864932889058,"y":0.6287121911392307},{"t":1730062470692,"x":0.14267291445186156,"y":0.6242401238264317},{"t":1730062470700,"x":0.14079717957483256,"y":0.6197680565136325},{"t":1730062470709,"x":0.1379835819549889,"y":0.6152959892008334},{"t":1730062470717,"x":0.1379835819549889,"y":0.6108239218880342},{"t":1730062470725,"x":0.13704571921217426,"y":0.6085877091053303},{"t":1730062470733,"x":0.13704571921217426,"y":0.6063522128278437},{"t":1730062470742,"x":0.1379835819549889,"y":0.601879787262436},{"t":1730062470750,"x":0.1398593168320179,"y":0.595171507166933},{"t":1730062470758,"x":0.14642438420591958,"y":0.5839915180112395},{"t":1730062470767,"x":0.15767878407669392,"y":0.5728111706029374},{"t":1730062470775,"x":0.17268466309292602,"y":0.5616308231946352},{"t":1730062470783,"x":0.18956623002918846,"y":0.5482146212562379},{"t":1730062470791,"x":0.20926142275949378,"y":0.5303263520050413},{"t":1730062470800,"x":0.2298944782326137,"y":0.5102018699711409},{"t":1730062470808,"x":0.2505275524885331,"y":0.48560532062444145},{"t":1730062470817,"x":0.2720984707044677,"y":0.4654815550957582},{"t":1730062470825,"x":0.2908558006919583,"y":0.4453567148092493},{"t":1730062470833,"x":0.3086752491538349,"y":0.42523223277534894},{"t":1730062470843,"x":0.3246189909128816,"y":0.40510810899405714},{"t":1730062470851,"x":0.341500595414743,"y":0.3827474141774529},{"t":1730062470858,"x":0.3546307113797469,"y":0.35591501030065814},{"t":1730062470867,"x":0.3733880413672375,"y":0.3201384717982651},{"t":1730062470875,"x":0.38276670636098287,"y":0.29777777698166086},{"t":1730062470883,"x":0.3902696458690989,"y":0.2754174404176652},{"t":1730062470892,"x":0.3968346850688014,"y":0.2530571038536696},{"t":1730062470900,"x":0.39964831086284425,"y":0.23516883460247306},{"t":1730062470908,"x":0.39964831086284425,"y":0.22175263266407566},{"t":1730062470917,"x":0.3987104105544307,"y":0.21057228525577357},{"t":1730062470925,"x":0.3968346850688014,"y":0.20386400516027056},{"t":1730062470933,"x":0.3902696458690989,"y":0.19268401600457705},{"t":1730062470942,"x":0.3846424318466121,"y":0.19268401600457705},{"t":1730062470950,"x":0.37807735508131074,"y":0.1949198705346723},{"t":1730062470958,"x":0.36963655283038005,"y":0.19715608331737616},{"t":1730062470967,"x":0.36400937637349223,"y":0.20386400516027056},{"t":1730062470975,"x":0.35838216235100545,"y":0.21504435256857268},{"t":1730062470983,"x":0.35275502345971654,"y":0.22846055450697006},{"t":1730062470992,"x":0.34712777187163085,"y":0.2508208910709657},{"t":1730062471000,"x":0.34243845815755763,"y":0.27765401145297763},{"t":1730062471008,"x":0.3377491068778855,"y":0.308958124389963},{"t":1730062471017,"x":0.33399765590662694,"y":0.344734662892356},{"t":1730062471025,"x":0.33118406767818304,"y":0.3827474141774529},{"t":1730062471034,"x":0.32743257914132556,"y":0.4185246691850631},{"t":1730062471042,"x":0.323681128170067,"y":0.45430084943484755},{"t":1730062471050,"x":0.3218054026844377,"y":0.4878415334071453},{"t":1730062471059,"x":0.3199296396332095,"y":0.5191460045967392},{"t":1730062471067,"x":0.3189917768903949,"y":0.5415066994133434},{"t":1730062471075,"x":0.3180539141475802,"y":0.5593953269171485},{"t":1730062471084,"x":0.31711605140476556,"y":0.5728111706029374},{"t":1730062471092,"x":0.3161781886619509,"y":0.5839915180112395},{"t":1730062471101,"x":0.3152403259191363,"y":0.5906994398541339},{"t":1730062471110,"x":0.31430246317632166,"y":0.601879787262436},{"t":1730062471117,"x":0.3152403259191363,"y":0.6085877091053303},{"t":1730062471127,"x":0.3161781886619509,"y":0.6175318437309286}];
layout.swipe = swipe;
layout.words = ["", "keyboard", ""];
const ctx = kbd.getContext("2d");
const rc = makeRenderContext(() => refreshRender());
updateRenderContext(rc, ctx, kbd, files, theme, layout);
function refreshRender() {
updateRenderContext(rc, ctx, kbd, files, theme, layout);
drawRenderContext(rc, ctx, kbd, layout);
}
addEventListener("resize", (event) => { refreshRender() });
refreshRender();
</script>
<script>
Fns.runInference = async (swipePath) => {
let t0 = swipePath.length > 0 ? swipePath[0].t : 0;
const reqStr = "\n" + swipePath.map((v) => "[" + v.x + "; " + v.y + "]" + (v.t - t0)).join("\n");
const reqEndPoint = "https://swipesrv.futo.tech/recognize";
return await (fetch(reqEndPoint, {
method: "POST",
headers: {
"Content-Type": "text/plain",
},
body: reqStr
})
.then(response => response.text())
.then(text => {
const words = text.split(",");
return {"results": words.map(v => {return {"word": v}}), "timing": {}};
}))
}
</script>
</body>
</html>
Sitemap
Кол-во: 0
XML-карта сайта для поисковиков
?
Sitemap.xml помогает поисковику быстрее находить и индексировать страницы. Особенно важен для крупных сайтов и новых страниц, на которые ещё нет входящих ссылок.
Robots.txt не содержит ссылку на карту сайта. Рекомендуется добавить карту сайта и указать ссылку на нее в robots.txt.
Внутренние ссылки
Не найдено
Ссылки на другие страницы своего сайта
?
Внутренние ссылки распределяют ссылочный вес между страницами и помогают поисковику обходить сайт. Пустые анкоры и ссылки на запрещённые robots.txt страницы — типичные ошибки.
Внутренних ссылок на странице 0 оптимально.
Внешние ссылки
Кол-во: 11
Ссылки на сторонние сайты
?
Исходящие внешние ссылки передают часть ссылочного веса на чужие сайты. Ссылки на авторитетные ресурсы безопасны; ссылки на мусорные сайты могут навредить репутации страницы.
Внешних ссылок на странице 11 слишком много. Спрячьте лишние ссылки в тег noindex или атрибут rel='nofollow'!
Показать внешние ссылки
| Url | Анкор | Анализировать |
|---|---|---|
| keyboard.futo.org |
Get FUTO Keyboard v0.1.29+
|
Анализировать url |
| huggingface.co |
See dataset on HuggingFace
|
Анализировать url |
| swipe.futo.org |
Contribute to our validation dataset
|
Анализировать url |
| arxiv.org |
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors text-2xl">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
</div>
<div class="text-left">
<div class="font-bold text-white">Technical Report</div>
</div>
|
Анализировать url |
| huggingface.co |
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors text-2xl">
🤗
</div>
<div class="text-left">
<div class="font-bold text-white">HuggingFace models</div>
<div class="text-xs text-slate-400"></div>
</div>
|
Анализировать url |
| gitlab.futo.org |
<div class="p-3 bg-slate-900 rounded-xl group-hover:bg-brand-glow group-hover:text-black transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
</div>
<div class="text-left">
<div class="font-bold text-white">Library</div>
<div class="text-xs text-slate-400"></div>
</div>
|
Анализировать url |
| futo.tech |
About FUTO
|
Анализировать url |
| github.com |
GitHub
|
Анализировать url |
| huggingface.co |
HuggingFace
|
Анализировать url |
| twitter.com |
Twitter
|
|
| chat.futo.org |
Zulip Chat
|
Анализировать url |
Конкуренты Готовность: 0%
Конкуренты в Яндексе
Кол-во: 0
Топ сайтов-конкурентов в Яндексе
?
Сайты, чаще всего появляющиеся в ТОПе Яндекса по запросам из семантического ядра этой страницы.
Мы не нашли у вас конкурентов в Яндексе. Сайт или очень молодой или плохо продвигается.
Конкурентов в ТОП-10 Яндекса не нашлось.
Конкуренты в Google
Кол-во: 0
Топ сайтов-конкурентов в Google
?
Сайты, чаще всего появляющиеся в ТОПе Google по запросам из семантического ядра этой страницы.
Конкуренты в Google тоже не найдены. Займитесь продвижением сайта!
Конкурентов в ТОП-10 Google не нашлось.
ЗоЗПП: права потребителей Готовность: 100%
Нарушения
Не выявлены
Признаков дистанционной продажи товаров (интернет-магазина) не обнаружено — требования ЗоЗПП о раскрытии информации продавца к сайту не применяются. Нарушений нет.
ФЗ-149: рекомендательные технологии Готовность: 100%
Нарушения
Не выявлены
Рекомендательные блоки («с этим покупают», «похожие товары» и т.п.) на сайте не обнаружены — требования ст. 10.7 ФЗ-149 к сайту не применяются. Нарушений нет.
ФЗ-38: реклама Готовность: 100%
Нарушения
Не выявлены
Рекламных тематик с обязательными оговорками (медицина, БАД, кредиты и займы, новостройки) на сайте не обнаружено. Нарушений нет.
ФЗ-436: защита детей Готовность: 100%
Нарушения
Не выявлены
Признаков информационной продукции (новости, видео, книги, игры, курсы) не обнаружено — обязательная возрастная маркировка по ФЗ-436 сайту не требуется. Нарушений нет.
Вердикт
Сайт swipe.futo.tech не совсем готов к продвижению (процент готовности лишь 50%). Для попадания в ТОПы поисковых систем нужно:
Исправьте ошибки в мета-тегах.
Исправьте ошибки оптимизации.
Исправьте ошибки индексации.
Поделитесь с друзьями: