IT-инфраструктура для бизнеса и творчества
Разработка
Artem Menchenya

Загружаем сайт в Google Play

В идеальном мире вы создаёте продукт и не думаете, как его портировать под все существующие платформы (web, Android, iOS, Windows). Мы ещё не в идеальном мире, но с развитием технологии Progressive Web Application (PWA) мы движемся туда уверенно и стремительно.

У вас есть интернет-магазин, веб-приложение или личный блог? Давайте сделаем так, чтобы он появился в магазине приложений Google.

Концепция

PWA — это технология, которая позволяет сайту получить функциональность мобильного приложения. Можно, в частности, реализовать работу приложения в офлайне, отправлять push-уведомления пользователям, взаимодействовать с внутренними службами и сервисами смартфона (геолокация, камера, панель уведомлений).

При установке из браузера на Android-смартфон или на компьютер с Windows PWA получает свой значок, ничем не отличающийся от других приложений.

В iOS нет возможности напрямую установить приложение, это можно сделать только добавив сайт на главный экран. Но мы уверены, что ребята из Купертино обязательно всё наверстают в самое ближайшее время. PWA можно добавить в Windows Store и Google Play (с небольшими усилиями).

В англоязычном интернете есть статьи о том, как добавить PWA-приложение в App Store, но мы это ещё не практиковали.

Шаг первый — создание PWA

Для реализации PWA нам потребуются сайт, Manifest и Service Worker.

Сайт

Свой пример мы будем строить на проекте «Чердак Дали», веб-ресурсе для художников. Единственное требование к сайту — SSL-сертификат (реализация https). Если у вас нет такой функции, рекомендуем обратиться к вашему хостинг-провайдеру, скорее всего, сертификат можно добавить бесплатно с помощью сервиса Let's Encrypt.

Нам нужен только доступ к файлам проекта, никакие серверные изменения или сложные механики не требуются.

Manifest

Нужно дать браузеру подробную информацию о нашем ресурсе: как он называется, как отображать его на конечном устройстве, какие иконки использовать. Это всё мы описываем в файле manifest.json. Мы не будем делать это вручную, а используем онлайн-генератор.

Предварительно нужно создать иконку для своего приложения размером 512 на 512 пикселей. Изображения остальных размеров сервис создаст самостоятельно. Заполняем все поля (название, короткое название, цветовая тема), выбираем режим отображения standalone, добавляем иконку и генерируем архив.

На выходе мы получим архив, в котором будут находиться сам файл manifest.json, а также иконки нашего приложения. Переносим эти файлы в корневую папку вашего сайта.

Service Worker

Service Worker позволяет выполнять фоновые процессы вроде кэширования страниц, веб-запросов, отправки уведомлений. В этой статье мы не будем углубляться в эту обширную тему, оставляйте комментарии, если хотите, чтобы мы разобрали создание и оптимизацию индивидуальных решений.

Для большинства задач хватит сгенерированного кода. Для этого воспользуемся PWA Builder.

  1. Вводим адрес своего сайта.
  2. Нажимаем на ссылку Choose a service worker.
  3. В новом окне выбираем подходящий вариант (в нашем случае мы выбрали Advanced Caching) и нажимаем на кнопку Download.

В итоге мы получим архив с двумя файлами. Файл pwabuilder-sw.js переносим в корневую папку сайта (к файлу manifest.json).

Нам нужно разместить файл pwabuilder-sw-register.js в корне сайта и просто добавить в конец нашего сайта (перед закрывающимся тегом body) новый тег script со ссылкой:

<script src=”/pwabuilder-sw-register.js”></script>

Альтернативный способ

Во втором файле хранится JavaScript-код для запуска service-worker. Этот код необходимо перенести в JS-файл вашего сайта. Чтобы определить, куда и в какой файл записать код, откройте сайт в браузере и зайдите в режим просмотра кода страницы (в Google Chrome это комбинация клавиш Ctrl+U).

Файлы JavaScript подключаются к сайту с помощью тега script. Ищем такой тег в начале (в секции head) или в конце сайта (перед закрывающимся тегом body).

Если тегов несколько (как правило присутствуют JS-файлы подключаемых библиотек), ищем самый последний из них. В теле тега содержится информация о том, где находится нужный нам файл (в нашем случае это файл app.js в папке js). В конец этого файла мы вставляем код из файла pwabuilder-sw-register.js.

На данном этапе у нас есть работающее PWA-приложение. При заходе на сайт с Android-смартфона внизу должна появится ссылка на установку приложения на главный экран.

Шаг второй — создание мобильного приложения

Для создания приложения нам потребуется бесплатная среда разработки Android Studio.

Начнём создание нашего проекта. На начальном экране Android Studio нажимаем Start a new Android Studio project. На следующем шаге нам предлагают выбрать заготовку для нашего приложения. Выбираем вариант Add No Activity и двигаемся дальше.

В следующем окне нужно заполнить информацию о новом приложении: имя, название пакета (уникальное имя приложения в Google Play), локация проекта на компьютере, язык программирования, а также минимальная версия операционной системы. Для нашего проекта мы заполнили информацию так:

Система создаст для нас новый проект. Для дальнейших действий нам нужно подключить к проекту библиотеку TWA Support Library. Для этого нужно открыть файл build.gradle:

Вначале внесём изменения в файл, у которого в описании стоит название вашего проекта (в нашем случае это файл build.gradle (Project: Cherdak)). Нам нужно добавить дополнительный репозиторий в секцию repositories:

maven { url 'https://jitpack.io' }
Пример дополнения кода

Теперь нам нужен файл, у которого в описании написано Module:app. Это файл, в котором мы можем добавить зависимости в наш проект. В секцию dependencies добавляем следующую запись:

implementation 'com.github.GoogleChrome:custom-tabs-client:a0f7418972'
Пример дополнения кода

Необходимая нам библиотека требует восьмой версии Java, поэтому нам нужно добавить её в опции компиляции. Для этого добавим следующий код в блок android:

compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
Пример дополнения кода

Нам также нужно специфицировать переменную manifestPlaceholders. Добавляем следующий код в секцию defaultConfig, меняя значения переменных на собственные:

manifestPlaceholders = [ hostName: "cherdak.org", defaultUrl: "https://cherdak.org", launcherName: "Cherdak" ]

На этом с файлом build.gradle мы закончили. Теперь нам нужно переместиться в файл AndroidManifest.xml. В этом файле хранится информация о нашем приложении.

Нам нужно добавить код для отображения нашего приложения:

<activity android:name="android.support.customtabs.trusted.LauncherActivity" android:label="${launcherName}"> <meta-data android:name="android.support.customtabs.trusted.DEFAULT_URL" android:value="${defaultUrl}" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="https" android:host="${hostName}"/> </intent-filter> </activity>

Обратите внимание, что мы раскрыли тег application.

Пример дополнения кода (раскрытый тег выделен синей рамкой)

Теперь нам нужно подтвердить взаимоотношение приложения и сайта. Связь должна быть установлена с обеих сторон — со стороны сайта и со стороны приложения. Для этого мы должны добавить изменения в файл build.gradle (Module: app). Не забываем менять значения на собственные:

assetStatements: '[{ "relation": ["delegate_permission/common.handle_all_urls"], ' + '"target": {"namespace": "web", "site": "https://cherdak.org"}}]'
Пример дополнения кода

Возвращаемся в файл AndroidManifest.xml и добавляем новые метаданные:

<meta-data android:name="asset_statements" android:value="${assetStatements}" />
Пример дополнения кода

Мы закончили устанавливать связь со стороны приложения. Теперь нам нужно установить связь со стороны сайта. Для этого сгенерируем файл assetlinks.json и разместим его на сервере. Для начала нам нужно сгенерировать APK-файл. Переходим во вкладку Build → Generate Signed Bundle or APK и выбираем APK.

Нам нужно сформировать новый keystore (или воспользоваться существующим, если он у вас есть). Нажимаем на кнопку Create new… и заполняем поля:

На следующей вкладке выбираем release и ставим галочки в Signature Versions:

После клика на клавишу Finish мы получим APK файл, который в дальнейшем сможем передать в Google Play.

Теперь перемещаемся во вкладку Tools → App Links Assistant.

Это откроет окно, в котором описаны шаги, необходимые для связывания сайта и приложения. В этом окне кликаем на кнопку Open Digital Asset Links File Generator и в открытом окне вводим URL сайта, ставим галочку на Select keystore file и выбираем ключ, который мы сформировали на предыдущем шаге:

Сохраняем полученный файл и переносим его в папку .well-known на сервер.

Пример дополнения кода

И нажимаем клавишу Link and Verify. В появившемся окне нажимаем OK. В итоге мы должны увидеть следующее:

Шаг третий — регистрируемся в Google Play, формируем материалы и выкладываем приложение

Для того, чтобы иметь возможность публиковать приложения в магазин Google Play, необходимо получить аккаунт разработчика. Для этого регистрируемся (стоимость аккаунта — $25 разовым платежом.

После регистрации у вас появится доступ к Google Play Console. Нажимаем на клавишу «Новое приложение». В следующем окне заполняем все необходимые данные (описание, возрастные ограничения, графические материалы).

Система сама подскажет, какие данные обязательны к заполнению. Мы не будем останавливаться на данном шаге: с нашей точки зрения, если вы дошли до этого шага, то вы определённо справитесь. Отметим только, что файл apk, который нужно загрузить, находится в папке «Папка с проектом/app/release/app-release.apk».

После загрузки пройдёт некоторое время, пока Google Play будет проверять ваше приложение. В конечном счёте вы сможете увидеть заветный статус «Опубликовано», после чего — ищите на просторах Google Play.

Спасибо, что прошли этот путь вместе со мной. Надеюсь, у вас всё получилось! Если нет, я постараюсь ответить на ваши вопросы. Обязательно поделитесь своими сайтами-приложениями в комментариях к этой статье.

Материал для вас готовила команда агентства Ilavista. Мы занимаемся разработкой сайтов и веб-приложений для бизнеса.

{ "author_name": "Artem Menchenya", "author_type": "self", "tags": ["\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f","googleplay"], "comments": 50, "likes": 39, "favorites": 123, "is_advertisement": false, "subsite_label": "dev", "id": 76260, "is_wide": true, "is_ugc": true, "date": "Mon, 22 Jul 2019 13:24:07 +0300", "is_special": false }
(function () { let cdnUrl = `https://specialsf378ef5-a.akamaihd.net/SelectelBranding/images/` let previousArticleNumber = null let currentArticleNumber = 0 let platform = 'Desktop' let articles = [ // { // name: 'camera', // url: `${cdnUrl}CameraCat`, // text: 'умную камеру для\u00A0наблюдения за\u00A0котиками', // link: '1', // }, { name: 'chill', url: `${cdnUrl}ChillCat`, text: 'трекер, который подскажет, когда пора отдохнуть', link: 'https://vc.ru/promo/288561-eye-tracker', }, // { // name: 'cloud', // url: `${cdnUrl}CloudCat`, // text: 'котика: даёшь ему «пять», а\u00A0он делает бэкап в облако', // link: '3', // } ] let buttonCycle = document.querySelector('.button--cycle') let textField = document.querySelector('.selectel-footer-subtitle') let imageAgent = document.querySelector('.image--agent') let banner = document.querySelector('.selectel-footer') buttonCycle.addEventListener('click', cycleClick) let media = window.matchMedia("(max-width: 570px)") media.addEventListener('change', matchMedia) function matchMedia() { if (media.matches) { platform = 'Mobile' } else { platform = 'Desktop' } update() } matchMedia() function cycleClick(event) { if (event) { event.preventDefault() event.stopPropagation() } window.open('https://vc.ru/tag/selectelDIY', '_blank') //cycle(event) } function cycle(event) { // incrementArticleNumber() textField.innerHTML = generatedText() imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?5' imageAgent.setAttribute("class", "") imageAgent.classList.add('image--agent', articles[currentArticleNumber].name) banner.href = articles[currentArticleNumber].link } function update() { banner.href = articles[currentArticleNumber].link imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?5' textField.innerHTML = generatedText() } function incrementArticleNumber() { previousArticleNumber = currentArticleNumber if (currentArticleNumber >= articles.length - 1) { currentArticleNumber = 0 } else { currentArticleNumber++ } } function generatedText() { let defaultText if (platform === 'Desktop') { defaultText = `Мы тут собрали %text%. Хотите почитать?` } else { defaultText = `Мы тут собрали %text%.` } return defaultText.replace('%text%', articles[currentArticleNumber].text) } function getRandom(min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min } (function create() { currentArticleNumber = getRandom(0, articles.length - 1) cycle() let page = document.querySelector('.page--entry') if (page) { function insertAfter() { let parents = page.querySelectorAll('[data-id="7"]') let referenceNode = parents[0] referenceNode.parentNode.insertBefore(banner, referenceNode.nextSibling); loaded() } setTimeout(() => insertAfter(), 0) } }()) function loaded() { banner.classList.add('loaded') } loadImages([ `${cdnUrl}CameraCatDesktop.svg`, `${cdnUrl}ChillCatDesktop.svg`, `${cdnUrl}CloudCatDesktop.svg`, `${cdnUrl}CameraCatMobile.svg`, `${cdnUrl}ChillCatMobile.svg`, `${cdnUrl}CloudCatMobile.svg`, ]) function loadImages(urls) { return Promise.all(urls.map(function (url) { return new Promise(function (resolve) { var img = document.createElement('img'); img.onload = resolve; img.onerror = resolve; img.src = url; }); })); } }())
0
50 комментариев
Популярные
По порядку
Написать комментарий...

Отношусь к этой идее с большим скепсисом. Когда человек качает приложение, он ожидает увидеть именно самостоятельное приложение, а не копию сайта. Это подразумевает знакомые паттерны поведения, которые существуют чтобы пользователю было проще ориентироваться в новых приложениях. Для этого используются схожие элементы дизайна, анимации, и т.д. И если вы не делали сайт в соответствии с гайдами Material (если мы говорим об Android), пользователь немного офигеет от вашего "приложения".

5

Если сайт делается не адаптивным - конечно пользователь офигеет. С другой стороны, большинство современных адаптивных сайтов использует те же элементы, что и мобильные приложения - бургер-меню, float-button, side navigation и т.д. используются так же, как и нативных приложениях.

2

Да не, если нормально делать, то они выглядят как приложения.

Правда гугл подводит и при первом открытии сообщает пользователю, что для работы приложения используется Google Chrome. Мои пользователи из-за этого обижаются)) А так бы совсем чисто всё выглядело.

1

Я давно пытался сделать что-то подобное на андроид для своего проекта "Taskker" https://taskker.club пока не узнал о существовании PWA и просто перевел сайт на эту технологию. В итоге отложил идею андроид-приложения в "долгий ящик" ибо "зачем платить больше, если не видишь разницы?".
Однако, по-моему среднестатистический пользователь будет ещё долго привыкать к PWA (и не факт, что привыкнет), он хочет видеть приложение в знакомом ему Play Market, поэтому данная статья очень даже актуальна. Спасибо!

P.S. По-моему в приложении неплохо было бы совсем убрать адресную строку (напишите как это сделать), чтобы у пользователя было ощущение, что это полноценное приложение, а не просто страница в браузере.

4

Мы продолжаем экспериментировать, обязательно напишем, если придумаем как убрать адресную строку. Спасибо за обратную связь!

2

нужна просто оболочка которая отобразит сайт внутри приложения без адресной строки?

1

Да, но проблема в том, что в нашем случае используется Trusted Web Activities, а она в свою очередь имеет адресную строку по-умолчанию. Мои знания в Android-разработке не очень высокие, я думаю что это нельзя обойти. Надеюсь, что ошибаюсь :)

1

Как-то начинал писать статью как обернуть сайт в приложение на iOS и Android, но забросил. Завтра тогда подниму материалы и размещу статью.

Я делал с 4-мя нижними табиками, чтобы хоть что-то было от мобильного приложения, иначе Apple не пропускает.

1

Будет интересно почитать

1

Так адресная строка же убирается сама. Важно, чтобы все URL приложения были продолжением start_url, и всё.

1

это не про twa, в pwa так

0

Есть еще такой сервис https://appmaker.xyz/pwa-to-apk/. Там не нужно возиться с Android Studio.
Стоит отметить что у пользователя должен быть свежий Google Chrome (пока только он поддерживает). Иначе приложение будет выглядеть как вкладка браузера. Технология называется вроде как TWA

3

Интересный сервис, еще не пробовали. Обязательно напишем рецензию. Технология называется Trusted Web Activity (TWA) - гугл тому, кому интересно глубже погрузиться в тему.

1
Ледяной украинец

Кордову не пробовали?

2

Нет, сбросьте ссылку

1

Не работает с templates , прощай SEO

0

Кордова другую задачу решает - упаковать приложние-подобный код js+html в apk, как обычный сайт оно не будет нормально работать. Тут же смысл в том, что у вас и сайт и приложение работают с одним кодом и сайт ещё висит в сети.

0
Ледяной украинец

что мешает подключать мобильный js-код, если он есть, и cordova.js только на этапе сборки мобильного приложения?

1

получилось ли убрать браузерную строку (у кого-то) и сделать Splash Screen ?

1

строка вот так убирается - в манифесте:

"display": "standalone",

Сплеш скрин - хз, я у себя сделал через роутер

0

речь не про pwa, а про twa.. там из-за доверенности не должно быть браузерной строки

0

Сам долго искал в чем проблема, в моем случае помогло правильная подпись приложения. Берется из plya console именно Сертификат для подписи приложения, не Загрузка сертификата. Цифровой отпечаток сертификата SHA-256 как только я поставил Цифровой отпечаток сертификата SHA-256 в файле assetlinks.json адресная строка пропала. 

0

да, так и есть, надо использовать гугловую подпись, не свою, хоть она и проходит валидацию

0

Отлично, ушла строка

0

хотелось бы увидеть более подробные комментарии к каждому из этапов. зачем и почему нужны те или иные вставки кода? хочется понимать

0

В таком случае получится очень длинный материал. Мы старались сделать максимально коротко, только практика. В дальнейшем будем разбирать отдельно каждый этап (создание индивидуальных service worker, настройка проекта в Android studio и т.д ), поэтому следите за обновлениями

0

Потому что размер сборки webpack-a 2 МБ. То есть прежде, чем сайт откроется, пользователь должен подождать, пока в его браузер подгрузится 2МБ JavaScript-кода.

Чтобы исправить:
1. Нужно минимизировать сборку webpack (см. UglifyPlugin). Но вообще новые версии webpack по умолчанию минимизируют.
2. Сжать дополнительно сборку gzip-ом.

Предполагаю, что в результате сборка составит килобайт 100 вместо 2МБ, а перфоманс подлетит 70+ ред.

1

Не в бровь, а в глаз 😉 Надеюсь, автор обратит внимание. ред.

0

Да, все верно. И полностью согласен, что косяк :) просто деплоим иногда по несколько раз в день - иногда забываем залить продакшн-файлы, а не девелопмент-файлы. Мы сейчас на стадии наращивания функционала, который уже давно закладывали, но все время откладывали. После будет этап оптимизации. В любом случае, спасибо за совет!

0

Нет, пока добавляем кучу функционала - после будем оптимизировать.

0

Приветствую. Хотелось бы увидеть результаты влияния на сервер до и после pwa. Просто у меня есть сайты, которые хочу перевести на pwa, но боюсь за сервак. Вдруг упадет? о.О

0

Все отлично автор написал. Мы занимаемся также разработкой и гарантированной публикацией таких приложений в App Store (несмотря на жесткую политику Apple)

 https://appverter.com

0

Очень интересно было бы почитать о том, как вы это делаете :) Или это корпоративная тайна?

0

Ещё какая тайна)

0

Добрый день! я по вашему туториалу сделал, но у меня иконки не загружаются. В чем может быть проблема не подскажите?

0

Вы можете установить Google Crome расширение Lighthouse. Запустите проверку и расширение покажет все проблемы сайта, в том числе почему не отрабатывает PWA

0

Добрый день!

Дошел до шага assetStatements: и все ломалось копирую код точно как у Вас ставлю свой сайт и андроид студио выдает мне ошибку

: 21: expecting ']', found 'assetStatements' @ line 21, column 17.
assetStatements: '[{ "relation": ["delegate_permission/common.handle_all_urls"], ' +
^

в чем может быть дело?

0

Подскажите пожалуйста, а если такая тема на IOS?

0

Давным давно такие приложения запрещены на App Store. С 2020 даже с минимальным нативным функционалом модераторы почти не пропускают. Нужна специальная система, мы публикуем такие приложения.

0

Все сделал по инструкции, но иконка приложения штатная от андроид, что может быть не так? Подскажите пожалуйста

0

Полный пиздец , блять сделал статью для галочки что бы было . А толку ноль . Тут не то что не пашет , просто из инфи что здесь велосипед не собрать ! Короче автор пидор !

0

Все сделал как вы описали. Файл apk сформировал, на смартфоне все работает.
Но приложение не цепляет файл манифеста из pwa. А когда публикуешь его в google play, и устанавливаешь его от туда, то вообще приложение работает как как стандартная страница браузера с адресной строкой в верху. Опять таки думаю по тому что приложение само по себе не связанно никак с pwa и просто открывает сайт как есть. И иконки не тянуться тоже, приложение устанавливается с дефолтной иконкой андроида ред.

0

Все сделал так, как вы описали. Однако столнкулся с тем, что приложение вручную установленное на смартфоне адресную строку не показывает. Тогда как уже опубликованное в Play Market - показывает. Причем, файлы одни и те же!
Кто может подсказать, в чем дело? Может файл assetlinks.json нужно еще куда загружать в Google Play Console?

0

те же яйца, все проверки проходит, возможно дело в android.support.customtabs.trusted.LauncherActivity
то что это просто ланчер а не обертка, а в хроме из-за политик безопасности показывается полюбас адресная строка. при добавлении на главный экран - не показыввается. но это не интересно. еще проблема из-за ланчера хрен сделаешь выход OnDestroy через двойное нажатие возврата назад.

0

Я таки нашел решение!!!
Как оказалось, нужно заменить имеющийся шифр SHA-256 в файле assetlinks.json на тот, который сгенерировал Google после загрузки в Play Market (раздел "сертификаты приложения" SHA-256). Таким образом, скачиваемое приложение уже будет показываться без адресной строки!

P.S. Если после замены шифра адресная строка еще показывается, то не забываем чистить кэш Chrome, ведь мы используем его технологии в работе TWA/PWA. )))

0
Читать все 50 комментариев
Жизнь и удаленная работа во Вьетнаме: почему работать со сдвигом на 4 часа — это хорошо

Чтобы разобраться, как живется во Вьетнаме на самом деле, мы пообщались с аналитиком, которая с 2013 года живет здесь с супругом и не собирается никуда уезжать. Она расскажет, какие тут на самом деле цены, как обстоят дела с развлечениями и медициной и в чем главное отличие Вьетнама от других стран Юго-Восточной Азии.

Хомяк-криптотрейдер заработал 30% за три месяца в «клетке для торговли» — обойдя Уоррена Баффета и S&P 500 Статьи редакции

Мистер Гокс занимается торговлей криптовалютой с 12 июня.

Как испортить лучшую систему комментариев в рунете на примере vc.ru

Иногда мне кажется, на vc.ru идёт прогресс дизайна ради дизайна, а не ради прогресса. Вот и до комментариев добрались.

Готовы выбрать победителя премии «Экспортер года eBay — 2021»?
Как традиционному малому бизнесу превратиться в стартап: план действий

Сейчас в России предприниматели переходят из традиционного малого бизнеса в стартапы очень редко — меньше чем в 0,02% случаев. Это не больше 1 000 стартапов из около 6 млн предприятий малого бизнеса. Поговорим о том, что мешает предпринимателям и как действовать, если есть желание создать стартап.

Прошел финал программы скаутинга в киберспорте Winstrike

Итоги программы Winstrike Scouting powered by UltraGear

Нужны ли в России сити-фермы

И появятся ли грядки на крышах пятиэтажек.

re-thinkingthefuture.com
Я скрестил «Трибуну» и Product Hunt

Теперь вы никогда не пропустите самые перспективные русскоязычные стартапы.

Создал ледовый комбайн в 50, а вместе с ним и рынок таких машин, который сразу захватил — это изобретатель Фрэнк Замбони Статьи редакции

Иногда ледозаливочные машины других производителей по ошибке называют «Замбони», пишет The Hustle.

Ледовый комбайн Замбони Time
Почему кейс с хомяком - не ошибка выжившего и не случайность

Сегодня Интернет облетела новость о том, что хомяк, бегая по своей клетке, заработал +29% на крипте. В комментария к публикации на VC многие писали, что это ошибка выжившего и просто случайность, которая ни о чем не значит. Я же постараюсь объяснить, почему это закономерность, и как сделать так, чтобы ваш кот/собака/рыбка показали такой же…

Увидеть первые Apple, сделать копию на ксероксе и потрогать мышку: как в Минске-88 прошла выставка «Информатика в США» Статьи редакции

Выставка стала потрясением для жителей: это было похоже на фильм «Назад в будущее», который на выставке тоже показывали, рассказывает dev.by. Издание поговорило с экс-гидом той выставки и минчанами, которые до сих пор хранят значки с её логотипом.

null