Как получить email пользователя в GetCourse через JavaScript

Ниже — JavaScript-пример для веб-страницы GetCourse, который получает email из ответа профиля и передаёт его в следующий шаг кода. Главное отличие от простого присваивания переменной: запрос асинхронный, поэтому использовать адрес нужно после завершения запроса.

Что проверить перед вставкой

  • Код запускается внутри нужного аккаунта GetCourse, в авторизованной веб-сессии, а не на постороннем сайте.
  • На странице есть window.jQuery, а window.accountUserId содержит ID того пользователя, чей профиль должен быть прочитан.
  • У текущей сессии есть доступ к запрашиваемому профилю. Наличие ID само по себе не выдаёт прав на его данные.
  • Ответ /c/sa/user/profile/ID в вашей версии интерфейса содержит success и массив data.blocks, где email находится в title.

Это обращение к внутреннему маршруту веб-интерфейса, а не обещание стабильного публичного API. Сначала проверьте формат ответа на тестовой странице своей школы. Не переносите этот сниппет автоматически на новый интерфейс или другой раздел аккаунта.

Получить email и дождаться ответа

function readGetCourseEmail() {
  if (!window.jQuery || !window.accountUserId) {
    return Promise.reject(new Error('Нет jQuery или ID пользователя'));
  }

  const userId = encodeURIComponent(String(window.accountUserId));

  return new Promise((resolve, reject) => {
    window.jQuery.getJSON('/c/sa/user/profile/' + userId)
      .done((response) => {
        if (!response || response.success !== true ||
            !response.data || !Array.isArray(response.data.blocks)) {
          reject(new Error('Неожиданный формат ответа профиля'));
          return;
        }

        const email = response.data.blocks
          .map((block) => block && typeof block.title === 'string' ? block.title.trim() : '')
          .find((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value));

        resolve(email || null);
      })
      .fail((xhr) => {
        reject(new Error('Не удалось прочитать профиль: HTTP ' + xhr.status));
      });
  });
}

readGetCourseEmail()
  .then((email) => {
    if (!email) {
      console.warn('В ответе профиля не найден email');
      return;
    }

    window.email = email;
    // Действие, которому нужен email, выполняйте здесь.
  })
  .catch((error) => console.warn(error.message));

Во вкладку JavaScript темы вставляют сам JS-код, без тегов <script>; для HTML-блока способ вставки другой. Как устроено подключение CSS и JavaScript в темах, описано в справке GetCourse. Код должен быть подключён только на страницах, где он нужен.

Почему window. email сразу после вызова пустая

Строки после readGetCourseEmail() продолжат выполняться, пока запрос ещё идёт. Поэтому следующий шаг надо поместить в .then(), как в примере, либо дождаться результата через await внутри асинхронной функции. Значение null означает, что запрос разобран, но email в ожидаемом поле не найден.

Если результат не пришёл

  • Откройте Network в инструментах разработчика и проверьте запрос профиля: статус ответа и его JSON-структуру.
  • При 403 сначала проверьте права и сессию; не пытайтесь обходить ограничение новым ID.
  • Если изменились имена полей или формат title, адаптируйте разбор ответа. Не подставляйте случайную строку вместо email.
  • Если код вообще не выполняется, проверьте место подключения и ошибки в Console. Для Safari есть инструкция по открытию консоли.

Как удалить тему в Геткурсе

Если тема оформления GetCourse больше не нужна, её можно удалить через адрес страницы редактирования. Ниже — короткий способ, показанный в видео, и проверки, которые стоит выполнить перед удалением. Речь о теме с CSS/JS, а не об удалении школы или учебных материалов.

Сначала отключить или сохранить тему

Если нужно только перестать применять оформление, сначала снимите назначение темы в настройках аккаунта, тренинга или страниц, где она подключена. Удаление нужно тогда, когда сама тема больше не понадобится. Области применения темы описаны в официальной справке GetCourse.

  • Скопируйте CSS, JavaScript, настройки и адреса дополнительных файлов в отдельную копию. Само видео резервную копию не создаёт.
  • Запишите, к каким страницам и тренингам привязана тема. Снятие оформления не должно стать неожиданностью для работающей школы.
  • Убедитесь, что открыли нужную тему и у вас есть право её редактировать. Если меняете оформление впервые, проверьте действие на ненужной тестовой теме.

Удалить тему через адрес страницы

Способ относится к странице редактирования, в адресе которой есть действие update. Если структура адреса другая, не подставляйте delete наугад — используйте доступные действия интерфейса или поддержку GetCourse.

  1. Откройте страницу редактирования нужной темы.
  2. В адресной строке замените только действие update на delete, оставив домен, ID и остальной адрес этой темы.
  3. Нажмите Enter, когда копия сохранена и выбрана именно та тема, которую нужно удалить. Переход по этому адресу относится к удалению, а не к предпросмотру.
  4. Вернитесь к списку тем и проверьте результат. Затем откройте страницы, где тема была назначена, под обычным пользователем.

Если оформление изменилось не так, как ожидали

Проверьте оставшиеся назначения темы и другие источники CSS/JS: настройки аккаунта, тренинга, отдельной страницы. Восстановление после удаления потребует сохранённых кода и настроек: можно создать тему заново и назначить её прежним страницам. Автоматическое восстановление удалённой темы этот способ не предусматривает.

Если после исправления кода приложение показывает старый вид, отдельно проверьте кэш приложения GetCourse. Очистка кэша не заменяет проверку того, какая тема назначена странице.

Не знаю сколько денег у торговцев KCD2

В игре есть баг с русским языком, слово «Продажа» закрывает количество денег у торговца. Из-за этого ты не понимаешь, насколько много можно товаров продать.

Когда даёшь много товаров и начинаешь торговаться, оказывается, что у торговца нет денег. Когда отменяешь торг, репутация понижается.

Я написал разрабам, они сказали, что баг приняли, но когда починят — не знают.

Пока я нашел решение — смотреть на то, как меняется цвет цифр при продаже, подробнее на видео. Если цифра белая — продолжайте скидывать товары. Если красная — денег у него столько нет.

Подробнее на видео

Как прижать footer к низу страницы: все способы с примерами

Прижатие футера к нижней части страницы — частая задача при вёрстке сайтов. Рассмотрим несколько надёжных способов её решения, от простых до более сложных.

1. Способ через Flexbox (рекомендуемый)

Самый современный и гибкий способ прижать футер к низу страницы — использование Flexbox:

<!-- HTML структура -->
<body>
    <div class="wrapper">
        <header>Шапка сайта</header>
        <main>Основной контент</main>
        <footer>Футер</footer>
    </div>
</body>
/* CSS стили */
.wrapper {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

main {
    flex: 1;
}

footer {
    margin-top: auto;
}

2. Метод отрицательных отступов

Классический способ, который работает даже в старых браузерах:

<!-- HTML структура -->
<div class="wrapper">
    <div class="content">
        <header>Шапка</header>
        <main>Контент</main>
    </div>
    <footer>Футер</footer>
</div>
html, body {
    height: 100%;
    margin: 0;
}

.wrapper {
    min-height: 100%;
}

.content {
    padding-bottom: 100px; /* Высота футера */
}

footer {
    height: 100px;
    margin-top: -100px;
    clear: both;
}

3. Решение через Grid Layout

Современный подход с использованием Grid:

<body>
    <div class="grid-container">
        <header>Шапка</header>
        <main>Основной контент</main>
        <footer>Футер</footer>
    </div>
</body>
.grid-container {
    min-height: 100vh;
    display: grid;
    grid-template-rows: auto 1fr auto;
}

header {
    /* стили шапки */
}

main {
    /* стили контента */
}

footer {
    /* стили футера */
}

4. Фиксированный футер

Если нужно, чтобы футер всегда оставался видимым:

footer {
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 60px;
    background-color: #f5f5f5;
}

main {
    margin-bottom: 60px; /* Отступ равный высоте футера */
}

5. Универсальное решение с CSS-переменными

Гибкий подход с использованием CSS-переменных для динамической высоты футера:

<div class="page-container">
    <main>Контент</main>
    <footer id="footer">Футер</footer>
</div>
:root {
    --footer-height: 60px;
}

.page-container {
    min-height: 100vh;
    padding-bottom: var(--footer-height);
    position: relative;
}

footer {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: var(--footer-height);
}
// Обновление высоты футера при изменении контента
function updateFooterHeight() {
    const footer = document.getElementById('footer');
    const height = footer.offsetHeight;
    document.documentElement.style.setProperty('--footer-height', height + 'px');
}

// Вызываем при загрузке и изменении размера окна
window.addEventListener('load', updateFooterHeight);
window.addEventListener('resize', updateFooterHeight);

Рекомендации по выбору метода

МетодПреимуществаНедостатки
FlexboxПростой, современный, гибкийНе поддерживается IE10 и ниже
Отрицательные отступыРаботает вездеСложнее поддерживать
GridОчень гибкийБолее новый, меньшая поддержка
FixedВсегда видимый футерЗанимает место на экране

Возможные проблемы и их решения

  • Если футер «прыгает» при изменении контента, проверьте min-height для контейнера
  • При использовании position: fixed учитывайте отступ для основного контента
  • Для динамического контента используйте JavaScript для пересчета высоты
  • При адаптивной вёрстке проверяйте работу футера на всех разрешениях

Заключение

Выбор метода прижатия футера зависит от требований проекта и необходимой поддержки браузеров, но в большинстве современных проектов рекомендуется использовать Flexbox или Grid Layout как наиболее гибкие и простые в реализации решения. Не забывайте тестировать выбранное решение на разных устройствах и при разном количестве контента.

Рерайт текста для инструкции по настройки таблицы

Исходный текст

Таблица может заменять в шаблоне переменные на ваши данные из таблицы. Для этого нужно указать переменные в {{двойных фигурных скобках}} в шаблоне.

Откройте пример шаблона и посмотрите, как в нем используются переменные.

Например, переменная {{Номер договора}} заменится на номер из колонки с названием «Номер договора».

☝️ Главное чтобы название колонки в таблице и переменная в шаблоне совпадали.

v1

Сейчас может показаться сложно, но постараемся обьяснить простым языком.

На прошлом шаге вы создали шаблоны документов. Там есть данные, которые нам нужно заменияться: ФИО, дату и другие данные.

Чтобы таблица поняла, куда вставлять наши данные из таблицы, нам нужно ей указать, куда и что вставлять.

Мы говорим ей: «Вставляй данные в этом место из этого столбца».

Чтобы указать название столбца, нам нужно вписать его в шаблоне в двойных фигурных скобках.

Таблица увидем в шаблоне {{Дата}}, вставит дату из столбца таблицы «Дата».

v2

На прошлом шаге вы создали шаблоны документов. Там есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие.

Чтобы таблица поняла, куда вставлять наши данные из таблицы, нам нужно ей указать, куда и что вставлять.

Мы говорим ей: «Вставляй данные в этом место из этого столбца».

Чтобы указать название столбца, нам нужно вписать его в шаблоне в двойных фигурных скобках.

Таблица увидем в шаблоне {{Дата}}, вставит дату из столбца таблицы «Дата».

v3

На прошлом шаге вы создали шаблоны документов. Там есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие другие.

Нам нужно показать таблице, какие данные куда нужно вставить.

Мы говорим ей: «Возьми данные из этого столбца и вставь в этом место в документе».

Чтобы указать название столбца, нам нужно вписать его в шаблоне в двойных фигурных скобках.

Таблица увидем в шаблоне {{Дата}}, вставит дату из столбца таблицы «Дата».

v4

На прошлом шаге вы создали шаблоны документов. В шаблонах есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие.

Нам нужно показать таблице, какие данные куда вставить.

Мы говорим ей: «Возьми данные из этого столбца и вставь в это место в документе».

В шаблоне нужно указать название столбца в двойных фигурных скобках: {{Дата}}.

Когда таблица будет создавать документ, она вставить данные из столбцов в те места, где мы ей это указали.

v5

На прошлом шаге вы создали шаблоны документов. В шаблонах есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие.

Нам нужно показать таблице, какие данные куда вставить.

Мы говорим ей: «Возьми данные из этого столбца и вставь в это место в документе».

В шаблоне нужно указать название столбца в двойных фигурных скобках: {{Сумма}} рублей».

В предложении это будет выглядеть так: «Заказазчик должен выплатить {{Сумма}} рублей».

После создания документа это будет выглядеть так: «Заказчик должен выплатить 13 000 рублей».

Когда таблица будет создавать документ, она вставить данные из столбцов в те места, где мы ей это указали.

v6

На прошлом шаге вы создали шаблоны документов. В шаблонах есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие.

Нам нужно показать таблице, какие данные куда вставить.

Мы говорим ей: «Возьми данные из этого столбца и вставь в это место в документе».

В шаблоне нужно указать название столбца в двойных фигурных скобках: {{Сумма}} рублей». Когда таблица будет создавать документ, она вставить данные из столбцов в те места, где мы ей это указали.

В шаблоне это будет выглядеть так: «Заказчик должен выплатить {{Сумма}} рублей».

В готовом документе туда подставятся реальные данные из столбца: «Заказчик должен выплатить 13 000 рублей».

Итоговый текст

На прошлом шаге вы создали шаблоны документов. В шаблонах есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие. 

Нам нужно сказать таблице: «Возьми данные из этого столбца и вставь в это место в документе». 

Там, где должны подставляться реальные данные, нам нужно указать название столбца в двойных фигурных скобках: {{Сумма}} рублей». 

Когда таблица будет создавать документ, она вставит данные из столбцов в те места, где мы ей это указали. 

Как это будет выглядеть: 
— в шаблоне: «Заказчик должен выплатить {{Сумма}} рублей». 
— в готовом документе: «Заказчик должен выплатить 13 000 рублей».

До и после

—— // ДО

Таблица может заменять в шаблоне переменные на ваши данные из таблицы. Для этого нужно указать переменные в {{двойных фигурных скобках}} в шаблоне.

Откройте пример шаблона и посмотрите, как в нем используются переменные.

Например, переменная {{Номер договора}} заменится на номер из колонки с названием «Номер договора».

☝️ Главное чтобы название колонки в таблице и переменная в шаблоне совпадали.

—— // ПОСЛЕ

На прошлом шаге вы создали шаблоны документов. В шаблонах есть данные, которые нам нужно заменять: ФИО, дату, номер договора, стоимость и другие. 

Нам нужно сказать таблице: «Возьми данные из этого столбца и вставь в это место в документе». 

Там, где должны подставляться реальные данные, нам нужно указать название столбца в двойных фигурных скобках: {{Сумма}} рублей». 

Когда таблица будет создавать документ, она вставит данные из столбцов в те места, где мы ей это указали. 

Как это будет выглядеть: 
— в шаблоне: «Заказчик должен выплатить {{Сумма}} рублей». 
— в готовом документе: «Заказчик должен выплатить 13 000 рублей».

Как заменить jQuery на современный JavaScript: основные методы

В современном веб-разработке jQuery становится всё менее необходимым благодаря развитию стандартного JavaScript. В этой статье мы рассмотрим, как заменить самые популярные методы jQuery на чистый JavaScript.

1. Выбор элементов (Селекторы)

jQuery версия:

// jQuery
$('.my-class')
$('#my-id')
$('div')
$('div.my-class')
$('.my-class:first')

Современный JavaScript:

// JavaScript
document.querySelector('.my-class')            // первый элемент
document.querySelectorAll('.my-class')         // все элементы
document.getElementById('my-id')               // по ID
document.getElementsByTagName('div')           // по тегу
document.querySelector('div.my-class')         // комбинированный селектор
document.querySelectorAll('.my-class')[0]      // первый элемент из коллекции

2. Манипуляции с DOM

Добавление и удаление классов

// jQuery
$('.element').addClass('new-class')
$('.element').removeClass('old-class')
$('.element').toggleClass('active')

// JavaScript
element.classList.add('new-class')
element.classList.remove('old-class')
element.classList.toggle('active')

Изменение содержимого

// jQuery
$('.element').html('<span>Новый контент</span>')
$('.element').text('Просто текст')

// JavaScript
element.innerHTML = '<span>Новый контент</span>'
element.textContent = 'Просто текст'

3. Работа с атрибутами

// jQuery
$('.element').attr('src', 'image.jpg')
$('.element').removeAttr('title')
$('.element').prop('checked', true)

// JavaScript
element.setAttribute('src', 'image.jpg')
element.removeAttribute('title')
element.checked = true

4. События

Простое добавление событий

// jQuery
$('.button').click(function() {
    console.log('Клик!')
})

// JavaScript
document.querySelector('.button').addEventListener('click', () => {
    console.log('Клик!')
})

Делегирование событий

// jQuery
$('#parent').on('click', '.child', function() {
    console.log('Клик по дочернему элементу')
})

// JavaScript
document.getElementById('parent').addEventListener('click', (e) => {
    if (e.target.matches('.child')) {
        console.log('Клик по дочернему элементу')
    }
})

5. AJAX запросы

Простой GET запрос

// jQuery
$.ajax({
    url: '/api/data',
    method: 'GET',
    success: function(response) {
        console.log(response)
    },
    error: function(xhr, status, error) {
        console.error(error)
    }
})

// JavaScript
fetch('/api/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error))

POST запрос с данными

// jQuery
$.ajax({
    url: '/api/data',
    method: 'POST',
    data: JSON.stringify({ name: 'John' }),
    contentType: 'application/json',
    success: function(response) {
        console.log(response)
    }
})

// JavaScript
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: 'John' })
})
    .then(response => response.json())
    .then(data => console.log(data))

6. Анимации

Простое скрытие/показ элементов

// jQuery
$('.element').hide()
$('.element').show()

// JavaScript
element.style.display = 'none'
element.style.display = 'block'

Плавное появление/исчезновение

// jQuery
$('.element').fadeOut()
$('.element').fadeIn()

// JavaScript
// CSS
.fade-transition {
    transition: opacity 0.3s ease-in-out;
}

// JavaScript
function fadeOut(element) {
    element.style.opacity = '0'
    setTimeout(() => {
        element.style.display = 'none'
    }, 300)
}

function fadeIn(element) {
    element.style.display = 'block'
    setTimeout(() => {
        element.style.opacity = '1'
    }, 10)
}

7. Работа с формами

// jQuery
$('form').serialize()
const value = $('input').val()

// JavaScript
const formData = new FormData(form)
const value = input.value

8. Манипуляции с размерами и позицией

// jQuery
const width = $('.element').width()
const offset = $('.element').offset()

// JavaScript
const width = element.offsetWidth
const rect = element.getBoundingClientRect()

9. Работа с коллекциями элементов

// jQuery
$('.items').each(function() {
    $(this).addClass('active')
})

// JavaScript
document.querySelectorAll('.items').forEach(item => {
    item.classList.add('active')
})

10. Утилиты для работы с массивами и объектами

// jQuery
$.extend({}, obj1, obj2)
$.merge(array1, array2)

// JavaScript
Object.assign({}, obj1, obj2)
[...array1, ...array2]

Полезные функции-помощники

// Функция для удобного выбора элементов
const $ = selector => document.querySelector(selector)
const $$ = selector => document.querySelectorAll(selector)

// Функция для добавления нескольких обработчиков событий
function addEventListeners(element, events, handler) {
    events.split(' ').forEach(event => {
        element.addEventListener(event, handler)
    })
}

// Функция для проверки наличия класса
function hasClass(element, className) {
    return element.classList.contains(className)
}

Советы по миграции с jQuery

  1. Постепенная миграция: Заменяйте jQuery код по частям, а не весь сразу
  2. Используйте полифилы: Для поддержки старых браузеров
  3. Тестируйте тщательно: Особенно в разных браузерах
  4. Документируйте изменения: Это поможет другим разработчикам

Преимущества перехода на чистый JavaScript

  1. Меньший размер кода (нет необходимости загружать jQuery)
  2. Лучшая производительность
  3. Современный и поддерживаемый код
  4. Лучшее понимание JavaScript
  5. Упрощенная отладка

Что такое Docker и как с ним работать

Что такое Docker?

Docker — это платформа для контейнеризации приложений. Если объяснять простыми словами, Docker позволяет «упаковать» приложение со всеми его зависимостями в стандартизированный блок (контейнер), который будет работать одинаково на любом компьютере с установленным Docker.

Ключевые преимущества Docker:

  • Изоляция: каждое приложение работает в своей среде
  • Переносимость: работает одинаково везде
  • Легкость: потребляет меньше ресурсов, чем виртуальные машины
  • Скорость: быстрый запуск и остановка
  • Версионирование: легко откатиться к предыдущей версии

Образы vs Контейнеры: В чём разница?

Образ (Image):

  • Это шаблон, только для чтения
  • Содержит инструкции для создания контейнера
  • Можно сравнить с классом в программировании
  • Не может быть изменен после создания
  • Хранится в репозитории (например, Docker Hub)

Контейнер:

  • Это запущенный экземпляр образа
  • Можно изменять во время работы
  • Имеет свою файловую систему
  • Можно сравнить с объектом класса
  • Исчезает после удаления (если не сохранять данные в volumes)

Пример отношения образа к контейнеру:

Образ Ubuntu → Можно запустить 10 разных контейнеров
Образ Nginx → Можно запустить несколько веб-серверов на разных портах

Основные команды Docker с примерами использования

Сценарий 1: Развертывание веб-приложения

# Создаем образ веб-приложения
docker build -t mywebapp:v1 .

# Запускаем контейнер с проброской портов
docker run -d -p 80:80 --name webapp mywebapp:v1

# Проверяем логи
docker logs webapp

# Перезапускаем при необходимости
docker restart webapp

Сценарий 2: Работа с базой данных

# Запускаем PostgreSQL
docker run -d \
    --name my-postgres \
    -e POSTGRES_PASSWORD=mysecretpassword \
    -v postgres-data:/var/lib/postgresql/data \
    -p 5432:5432 \
    postgres:13

# Подключаемся к базе
docker exec -it my-postgres psql -U postgres

Сценарий 3: Разработка с горячей перезагрузкой

# Запускаем контейнер с монтированием локальной директории
docker run -d \
    --name dev-env \
    -v $(pwd):/app \
    -p 3000:3000 \
    node:14 \
    npm run dev

Часть 2: Docker Compose

Что такое Docker Compose?

Docker Compose — это инструмент для определения и запуска многоконтейнерных приложений. Он использует YAML-файлы для конфигурации служб приложения и позволяет запускать все службы одной командой.

Основные возможности Docker Compose:

  • Управление несколькими контейнерами как единым приложением
  • Сохранение конфигурации в файле
  • Управление сетями и томами
  • Масштабирование служб

Практические сценарии использования Docker Compose

Сценарий 1: Веб-приложение с базой данных

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/dbname

  db:
    image: postgres:13
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=dbname

volumes:
  postgres-data:

Сценарий 2: Микросервисная архитектура

# docker-compose.yml
version: '3.8'
services:
  frontend:
    build: ./frontend
    ports:
      - "80:80"

  backend:
    build: ./backend
    ports:
      - "8080:8080"
    depends_on:
      - db
      - redis

  db:
    image: mysql:8
    volumes:
      - db-data:/var/lib/mysql

  redis:
    image: redis:6
    volumes:
      - redis-data:/data

volumes:
  db-data:
  redis-data:

Сценарий 3: Разработческое окружение

# docker-compose.yml
version: '3.8'
services:
  dev:
    build: 
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    command: npm run dev

Команды Docker Compose с пояснениями

# Запуск всех сервисов в фоновом режиме
docker-compose up -d

# Просмотр логов конкретного сервиса
docker-compose logs backend

# Остановка всех сервисов с удалением контейнеров
docker-compose down

# Пересборка и запуск определенного сервиса
docker-compose up -d --build frontend

# Масштабирование сервиса
docker-compose up -d --scale backend=3

Типичные сценарии отладки

Проверка состояния сервисов

# Статус всех сервисов
docker-compose ps

# Использование ресурсов
docker-compose top

Доступ к контейнерам

# Выполнение команды в контейнере
docker-compose exec backend bash

# Просмотр логов в реальном времени
docker-compose logs -f

Где в лагере цыган моя кровать? KCD2

После выполнения квеста «Miri Fajta» вы получите возможность переночевать в цыганском таборе.

После завершения квеста вам будет разрешено спать в таборе. Проблема в том, что вам не говорят, где ваша кровать

Ваша кровать напротив палатки барона, справа от травницы будет бело-красная палатка, ваша кровать — слева.

5 клиентских манипуляций на фрилансе и как на них реагировать

Ниже — пять ситуаций из моей работы с клиентами и фразы, которые помогают вернуть разговор к объёму, срокам и оплате. Важно различать обычное уточнение задачи и попытку получить дополнительные работы вне договорённостей.

1. Бесплатная правка как условие завершения проекта

Недавно у меня был показательный случай. Клиент говорит: «просьба сделать правку и мы готовы финалить». Казалось бы, просто просьба, да? 🤔

Но это манипуляция. Смотрите: правки не входят в стоимость, я об этом написал и руководитель сказал, что все допы — после. А тут игра в слова: «вы нам правочку — мы согласимся закончить». То есть клиент ставит условие: либо я делаю бесплатную работу, либо они не подпишут акт. Это шантаж в чистом виде 😅

2. Давление ожиданиями и обесцениванием

Вот ещё один шедевр из реальной переписки:

  • «У нас было представление об услуге, значит было ложно завышено от нее ожидание»
  • «И ни я, ни мои коллеги не смогли понять, что нам нужно еще что-то делать дополнительно, покупая услугу под ключ за 200к»
  • «нам не демонстировался полуфабрикат, который мы получили»
  • «немного шокирующая ситуация»

Видите, как красиво? Сначала идёт обесценивание работы через «полуфабрикат», потом давление суммой «200к», и в конце эмоциональное усиление через «шокирующую ситуацию». А по факту — всё сделано по договору, просто клиент хочет больше, чем заплатил 🤷‍♂️

3. Передача горящего дедлайна исполнителю

Эта ситуация бывает постоянно: клиент приходит с горящим дедлайном и хочет, чтобы вы успели. По сути, он передаёт вам свою ответственность. Если проект горит — это результат паршивой работы цепочки специалистов до вас. Если бы все сработали отлично, вам бы принесли задачу с запасом по времени.

Помню случай: приходит клиент и говорит: «Даниил, нам бы Геткурс оформить, у нас 2 дня». А мы это делаем минимум за 5 дней. И я, как дурак, повёлся 🤦‍♂️ Хотел помочь клиенту, начал думать, как сделать работу быстрее. В итоге с командой работали в авральном режиме, наделали багов, получили кучу претензий. Отношения с клиентом испортились.

4. Бесконечные дополнительные правки

О, это отдельный вид искусства! Клиент начинает с одной маленькой правки, потом ещё одной, и ещё… В одном из проектов клиент после сдачи составил список из 45 правок. Всякую дурь типа «подвиньте на 2 пикселя» и «сделайте кнопку темнее на 4%».

Знаете, что произошло, когда я выставил счёт на 60 000₽ за эти правки? Список волшебным образом сократился до 3 пунктов 😂 О чём это говорит? О том, что остальные 42 пункта были бесполезными правками, которые никакой ценности для бизнеса не несли.

Как защищаться от манипуляций с правками

  1. Предупреждайте заранее. Пропишите в договоре, что значит «правка». Чтобы потом можно было ссылаться на договор.
  2. Запрашивайте полный список правок. Если клиент просит «поменять цвет ссылок» — это наживка. За ней пойдет ещё и ещё. Просите сразу полный список.
  3. Не поддавайтесь на манипуляции типа:
    — «Сделайте пожалуйста, мы ведь вам столько заплатили»
    — «Мы же вроде хорошо поработали, можете пойти на встречу?»
    — «Я знаю как это делается, там это править 5 минут»
  4. Чётко обозначайте границы. Если решили сделать одну небольшую правку бесплатно — так и скажите: «Эту сделаем, остальные за доп. плату».

5. Перенос задач клиента на исполнителя

Недавно был диалог:

— Даниил, добавьте эту панель в тренинги, она в макете есть
— Михаил, мы занимаемся только дизайном и программированием, добавление панели в тренинги не входит в нашу работу
— Даниил, сделайте пожалуйста, чтобы все как в макете было, у нас не много тренингов (70 кстати 😄)

И так по кругу. Клиент пытается переложить на вас работу, которую должен делать сам. Тут надо быть готовым даже к разрыву договора, но не поддаваться.

Конкретные фразы для защиты

  • На «сделайте быстро, там на 5 минут»:
    «Пожалуйста, пришлите итоговый список правок, мы оценим его стоимость, сроки и внесем изменения»
  • На «но мы же вам столько заплатили»:
    «Да, я понимаю. Мы это очень ценим. Мы выполнили все работы согласно договору. Дополнительные работы оцениваются отдельно»
  • На «просто маленькая правочка»:
    «Давайте я оценю объем работ и пришлю стоимость. Даже небольшие изменения требуют знаний и времени.»
  • На «шокирующая ситуация»:
    «Давайте обратимся к договору и посмотрим, что входит в объем работ»

Главное правило

Всегда отстаивайте своё право не делать дополнительную работу. За 8 лет и 400+ проектов я успел много дров наломать. Самое опасное — брать на себя ту работу, которую ты делать не должен. Это забирает время с семьей, время отдыха и жизни.

Живем один раз. Проектов будет сотни, если не тысячи. А близкие люди, здоровье, жизнь — это штучные экземпляры. Берегите себя и своё время! 🙌

Когда объём задачи заранее неясен, полезно отдельно договориться об оплате итераций и дополнительных работ. В заметке о переходе на почасовую ставку есть пример сообщения клиенту и порядок работы по этапам.

Исправляем баг форма продамуса

Если вы забыли заполнить хоть одно поле в продамусе, он перезагрузит страницу, сотрет все поля. Я написал код, который это предотвращает.

Вставлять в расширение user js/css

$(document).ready(function() {
    // Экранирование квадратных скобок в селекторах для input-ов
    const requiredFields = [
        'textarea[name="paid_content"]',
        'input[name="order_id"]',
        'input[name="customer_email"]',
        'input[name="products\\[cur_1\\]\\[price\\]"]',
        'input[name="products\\[cur_1\\]\\[name\\]"]'
    ];

    function restoreFields() {
        requiredFields.forEach(selector => {
            const $field = $(selector);
            const fieldName = $field.attr('name');
            const savedValue = localStorage.getItem(fieldName);
            
            if (savedValue !== null && $field.length) {
                $field.val(savedValue);
            }
        });
    }

    function saveField($field) {
        const fieldName = $field.attr('name');
        const fieldValue = $field.val();
        localStorage.setItem(fieldName, fieldValue);
    }

    function findEmptyField() {
        let emptyField = null;
        
        for (let selector of requiredFields) {
            let $field = $(selector);
            
            // Если поле находится в скрытом контейнере, пропускаем его проверку
            if ($field.closest('.paygoods-product-row.hidden').length) {
                console.log('Пропускаем поле:', selector, 'так как его родитель скрыт');
                continue;
            }
            
            console.log('Проверяем поле:', selector, 'Найдено:', $field.length, 'Значение:', $field.val());
            
            if ($field.length && !$field.val().trim()) {
                console.log('Найдено незаполненное поле:', selector);
                return $field;
            }
        }
        return null;
    }

    function scrollTo($element) {
        if (!$element || !$element.length) return;
        
        console.log('Скроллим к:', $element[0].name || $element[0].id);
        
        let offset = $element.offset().top;
        console.log('Отступ элемента:', offset);
        
        $('html, body').animate({
            scrollTop: offset - 100  // корректируйте смещение при необходимости
        }, {
            duration: 800,
            complete: function() {
                $element.focus();
                $element
                    .css('background-color', '#ffe6e6')
                    .delay(200)
                    .queue(function(next) {
                        $(this).css('background-color', '');
                        next();
                    });
            }
        });
    }

    // Обработчик для всех кнопок
    $('#btnPay-wrapper button, .dropdown-menu button').on('click', function(e) {
        console.log('Кнопка нажата');
        
        let emptyField = findEmptyField();
        console.log('Найдено незаполненное поле:', emptyField ? emptyField[0].name : 'нет');
        
        if (emptyField) {
            e.preventDefault();
            e.stopPropagation();
            scrollTo(emptyField);
            return false;
        }
        // Если незаполненных полей нет – форма отправляется как задумано
    });

    function updateButtonState() {
        let hasEmptyFields = findEmptyField() !== null;
        
        // Обновляем внешний вид кнопок, не блокируя их кликабельность
        $('#btnPay-wrapper button, .dropdown-menu button').each(function() {
            $(this).toggleClass('btn-disabled', hasEmptyFields);
            // Не отключаем кнопки:
            // $(this).prop('disabled', hasEmptyFields);
        });
    }

    // Сохраняем значения при вводе
    $(requiredFields.join(',')).on('input change', function() {
        saveField($(this));
        updateButtonState();
    });

    // Восстанавливаем значения при загрузке страницы
    restoreFields();
    
    // Начальное состояние кнопок
    updateButtonState();

    // Очистка localStorage при успешной отправке формы
    $('form').on('submit', function() {
        requiredFields.forEach(selector => {
            const $field = $(selector);
            const fieldName = $field.attr('name');
            localStorage.removeItem(fieldName);
        });
    });
});
/* Стили для визуальной индикации "заблокированной" кнопки */
.btn-disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.btn-inactive {
opacity: 0.7;
cursor: not-allowed !important;
}
.highlight-field {
animation: highlight-pulse 2s ease-in-out;
}
@keyframes highlight-pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); }
}

.btn-disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

body {
  font-family: 'Manrope', Roboto !important;
}

#table-instruction {
  background: #555CFF !important;
  color: #fff !important;
}

#table-instruction a {
  color: #fff !important;
  text-underline-offset: 3px !important;
  text-decoration-thickness: 1px !important; 
}

h1 .editor-pane-line {
  color: #fff;
  line-height: 1.3
}

.hint-text {
  opacity: 1 !important;
}

form > :nth-child(9) span {
  display: none !important;
}

form .input-group-addon {
  display: none !important;
}

form .col-xs-6 .h-45,
input[type="text"],
select,
.jq-selectbox__select,
.form-control{
  border-radius: 6px !important;
  border-color: #aaa!important;
  border-width: 1px !important;
}
.btn.btn-sm {
  padding: 10px 20px !important;
  height: auto !important;
  margin: auto !important;
  border: none !important;
  transition: all .3s;
}

.btn.btn-sm * {
  transition: .3s all;
}

.btn.btn-sm:hover {
  background-color: rgb(219, 219, 219) !important;
  color: #fff !important;
  opacity: 1 !important;
}

.btn.btn-sm:hover span,
.btn.btn-sm:hover i {
  color: #222 !important;
}

.paygoods-product-row {
  border: 1px solid #eee !important;
  padding: 20px !important;
  border-radius: 6px !important;
}

.paygoods-product-add,
.paygoods-product-delete {
  top: -10px !important;
  padding: 10px !important;
  height: 20px !important;
  right: -10px;
}

.paygoods-product-delete {
  background-color: rgb(255, 81, 81) !important;
  color: #fff !important;  
}
.paygoods-product-delete i {
  color: #fff !important;  
}

.price-box {
  background-color: #f0f0ff !important;
  color: #555CFF !important;
  font-size: 20px !important;
  font-weight: bold;
}

button[type="submit"] {
  color: #fff !important;
  background-color: #555CFF !important;
  border: none !important;
  margin-bottom: 30px !important;
}

.dropup-payform {
  width: 100% !important;
}

.dropdown-menu {
  position: relative !important;
  display: flex !important;
  border: none !important;
  background: none !important;
  box-shadow: none !important;
  justify-content: space-between !important;
  width: 100% !important;
  gap: 10px;
}

#dropdownMenu2 {
 display: none !important;
}

.dropup.dropup-payform .dropdown-menu:after,
.dropup.dropup-payform .dropdown-menu:before {
  display: none !important; 
}

.dropdown-menu li {
    width: 100%;
}

.dropdown-menu li button {
  width: 100%;
  border-radius: 6px !important;
  text-align: center !important;
  background-color: #f1f1f1 !important;
  color: #222 !important;
}

#btnPay-wrapper button{
    width: 100% !important;
}

Figma: полное руководство по установке и использованию в 2025 году

Figma стала стандартом в мире веб-дизайна и прототипирования. В этом руководстве мы разберем всё, что нужно знать о программе: от установки до практического применения.

Что такое Figma?

Figma — это профессиональный инструмент для веб-дизайна и прототипирования, который работает как в браузере, так и через десктопное приложение. Главное преимущество Figma — возможность совместной работы в реальном времени и cloud-first подход к хранению проектов.

Как скачать и установить Figma

Существует несколько способов начать работу с Figma:

  • Через браузер на figma.com (онлайн-версия)
  • Установка десктопного приложения
  • Мобильное приложение для просмотра проектов

Пошаговая инструкция по установке десктопной версии:

  1. Перейдите на страницу скачивания
  2. Выберите версию для вашей операционной системы
  3. Нажмите на ссылку
  4. Скачатйе и запустите установщик
  5. Следуйте инструкциям мастера установки

Figma онлайн: преимущества веб-версии

Онлайн-версия Figma предлагает ряд уникальных преимуществ:

  • Работа без установки дополнительного ПО
  • Автоматическое сохранение в облаке
  • Мгновенный доступ с любого устройства
  • Улучшенная производительность на слабых компьютерах

Figma Community: доступ к готовым ресурсам

Figma Community — это платформа, где дизайнеры делятся своими работами, шаблонами и компонентами. Здесь можно найти:

  • UI киты и дизайн-системы
  • Готовые шаблоны для разных типов проектов
  • Иконки и иллюстрации
  • Обучающие материалы

Сравнение версий Figma

ФункцияБесплатная версияProfessionalOrganization
Количество проектов3 файлаНеограниченноНеограниченно
Совместная работа2 редактораНеограниченноНеограниченно
Версионирование30 днейНеограниченноНеограниченно

Системные требования

Для комфортной работы с Figma необходимо:

  • Windows 7+ или macOS 10.13+
  • Процессор: Intel или AMD с 2 ядрами
  • Оперативная память: минимум 4 GB
  • Стабильное интернет-соединение
  • Современный браузер (Chrome, Firefox, Safari)

Начало работы с Figma

После установки и регистрации вы можете:

  1. Создать новый проект
  2. Присоединиться к существующей команде
  3. Изучить обучающие материалы
  4. Исследовать Community файлы
  5. Настроить рабочее пространство

Преимущества Figma перед другими редакторами

Figma выделяется среди конкурентов следующими особенностями:

  • Работа в браузере без установки
  • Совместное редактирование в реальном времени
  • Автоматическое сохранение
  • Мощные инструменты прототипирования
  • Встроенная система компонентов
  • Активное сообщество

Заключение

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

(Решение) Не работает аудио диалогов в Kingdom Come Deliverance 2

На релизе в игре отвалились диалоги. Персонажи говорят с закрытым ртом, видно только субтитры. И в игре, в кат-сценах.

При попытке сохранить настройки игры появляеся такая ошибка.

Решение для Geforce NOW

Зайдите в настройки клиента, прокрутите вниз и в правом столбце выберите язык игры «Английский (Великобритания)».

Решение для Steam

Найдите и откройте KCD 2. Нажмите на шестеренку и выберите «Свойства»

Выберите английский язык в выпадающем меню. Это не повлияет на язык игры.

После появится кнопка обновления. Обновите игру и продолжайте играть 💜

Размер поля должен быть соразмерен будущему содержанию

Часто встречаю этот баг, просят расписать поэму, а поле для этого из одной строки.

Как надо

Прикиньте, сколько пользователь может добавить туда информации. Можете попробовать заполнить сами и посмотреть, сколько получилось.

Пример с сайта remake. space

Топ-30 популярных плагинов для Figma в 2025

Плагины для Figma — это мощные инструменты, которые автоматизируют рутину, расширяют функционал и экономят до 40% времени дизайнера. В этой статье собраны 30+ самых востребованных плагинов 2025 года с прямыми ссылками.

1. Автоматизация и организация

1.1 Autoflow

  • Для чего: Автоматическое создание стрелок между фреймами
  • Особенности: Поддержка пользовательских стилей линий, интеллектуальное выравнивание
  • Ссылка

1.2 Rename It

  • Для чего: Пакетное переименование слоёв по шаблонам (например: «Button/Active»)
  • Лайфхак: Используйте маски вида «{type}-{num}» для системного подхода
  • Ссылка

1.3 Content Reel

  • Для чего: База готового контента: имена, даты, аватары, локали
  • Пример: Заполнение 50 карточек товаров за 2 клика
  • Ссылка

2. Работа с изображениями

2.1 Unsplash

  • Для чего: 5 млн+ стоковых фото прямо в интерфейсе Figma
  • Фишка: AI-поиск по стилю и цветовой палитре
  • Ссылка

2.2 Remove BG

  • Для чего: Удаление фона с точностью до 98% за 3 секунды
  • Ссылка

2.3 Image Tracer

  • Для чего: Конвертация растровых изображений в вектор
  • Важно: Идеально для логотипов и иконок
  • Ссылка

3. Анимация и 3D

3.1 LottieFiles

  • Для чего: 400k+ готовых анимаций и экспорт в JSON/GIF
  • Пример: Создание микроинтеракций для кнопок
  • Ссылка

3.2 Isometric

  • Для чего: 3D-преобразование объектов без Blender
  • Ссылка

3.3 3D Wave

  • Для чего: Создание «жидких» текстур и динамичных фонов
  • Ссылка

4. Типографика и текст

4.1 Stark

  • Для чего: Проверка контраста и симуляция дальтонизма
  • Статистика: Увеличивает доступность интерфейсов на 67%
  • Ссылка

4.2 Typograf

  • Для чего: Автокоррекция типографики: кавычки, тире, пробелы
  • Поддержка: Русский, английский, украинский языки
  • Ссылка

4.3 Better Font Picker

  • Для чего: Визуальный каталог шрифтов с предпросмотром
  • Ссылка

5. Доступность и тестирование

5.1 Contrast

  • Для чего: WCAG-анализ цветовых сочетаний
  • Фича: Автоподбор альтернативных оттенков
  • Ссылка

5.2 Dark Mode Magic

  • Для чего: Автоматическая конвертация в тёмную тему
  • Ссылка

6. Экспорт и разработка

6.1 Anima

  • Для чего: Конвертация дизайна в React/HTML/CSS-код
  • Интеграции: Поддержка Tailwind и Vue. js
  • Ссылка

6.2 html.to.design

  • Для чего: Импорт сайтов в Figma как редактируемых слоёв
  • Ссылка

6.3 Figma to Webflow

  • Для чего: Прямой экспорт в Webflow с сохранением структуры
  • Ссылка

Как устанавливать плагины: 3 способа

  1. Через меню Plugins (Shift+I → Поиск)
  2. Из Figma Community (Explore → Plugins)
  3. По прямой ссылке (как в этой статье)