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

После использования этого кода переменная будет доступна как email

$.getJSON( "/c/sa/user/profile/"+window.accountUserId, function( userdata ) {
  if(typeof userdata.success !== "undefined" && userdata.success === true) { 
    $.each(userdata.data.blocks, (i,bl)=>{ if(typeof bl.title !== "undefined" && /(.+)@(.+){2,}.(.+){2,}/.test(bl.title)) window.email = bl.title; });
  }
});

Не знаю сколько денег у торговцев 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 и как с ним работать

Часть 1: 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» вы получите возможность переночевать в цыганском таборе.

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

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

10 клиентских манипуляций, которые должен знать каждый фрилансер (и как на них реагировать)

Хитрые манипуляции с правками

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

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

Манипуляция «ожиданиями»

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

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

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

Манипуляция дедлайном

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

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

Манипуляция «бесконечными правками»

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

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

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

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

Манипуляция «мы всё сами»

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

— Даниил, добавьте эту панель в тренинги, она в макете есть
— Михаил, мы занимаемся только дизайном и программированием, добавление панели в тренинги не входит в нашу работу
— Даниил, сделайте пожалуйста, чтобы все как в макете было, у нас не много тренингов (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

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

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

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

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

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

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

Решение для Steam

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

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

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

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

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

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

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

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

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

Как надо

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

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

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

Топ-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. По прямой ссылке (как в этой статье)