Разработка сайтов в Санкт-Петербурге. Воссоздайте эффект сбоя кнопки Cyberpunk 2077 в CSS

 
 

Если вы хоть немного интересуетесь видеоиграми, вы, несомненно, знаете о Cyberpunk 2077. Это одна из самых ожидаемых игр 2020 года. Мир, который она рисует, имеет определенный стиль. Веб-сайт игры прекрасно передает эту эстетику. Его дизайн отлично передает внешний вид и ощущение. Как вы можете себе представить, это означает, что у него есть несколько довольно привлекательных компонентов пользовательского интерфейса.

Кто-то сначала обратился ко мне с вопросом, как я создам эффект изображения, используемый на сайте. Если вы наведете курсор на изображения в галерее изображений, у них появится аккуратный эффект «шума».

Эффект мерцающего шума при наведении курсора на сцену

Я принял вызов. Я копался, чтобы взглянуть на источник сайта. Немного покопавшись, я обнаружил, что это реализовано с помощью шейдеров и WebGL. Я совершенно новичок в написании шейдеров и WebGL. Это подтолкнуло меня попробовать. Но пока я отложил изучение кода WebGL и шейдеров на второй план.

Что привлекло наше внимание, когда я начал осматривать сайт в прямом эфире, так это аккуратные глючные кнопки эффектов. Я не новичок в создании глючных эффектов с помощью CSS. Мы решили, что я попытаюсь воссоздать их.

Мерцающая кнопка «ДОСТУПНО СЕЙЧАС»

И вот как вы можете это сделать!

Кнопка эффекта

Начнем с разметки:

 

 

Сначала нам нужно отсортировать размер, цвет и шрифт. Лучший способ сделать это правильно? Погрузитесь в источник и посмотрите, как это делается. При первом осмотре мы видим, что используется пользовательский шрифт. (Вы можете увидеть прямую ссылку на него в блоке кода ниже.)

Давайте создадим пользовательское правило @font-face:

@font-face {

font-family: Cyber;

src: url («https: //assets.codepen.io/605876/Blender-Pro-Bold.otf») ;

font-display: swap;

}

Как только мы это сделаем, мы можем установить базовый стиль. Использование переменных CSS для таких вещей, как цвет и размер шрифта, даст нам дополнительные возможности позже. Это также является причиной использования цветового пространства HSL. Мы покажем, почему позже.

—primary: hsl (var (—primary-hue), 85%, calc (var (—primary-lightness, 50) * 1%));

shadow-primary: hsl (var (—shadow-primary-hue), 90%, 50%) ;

primary-hue: 0;

primary-lightness: 50;

—color: hsl (0, 0%, 100%) ;

font-size: 26px;

shadow-primary-hue: 180;

Объединив это, мы получаем отправную точку. Обратите внимание, как мы используем тень вставки вместо границы для этой синей линии? Это потому, что граница сбила бы наш текст с центра. Тень вставки не повлияет на выравнивание текста.

Обрезанный угол

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

Мы можем обрезать угол, используя clip-pathсвойство:

clip-path: polygon (-10% -10%, 110% -10%, 110% 110%, 10% 110%, -10% 40%) ;

Обратите внимание, что мы не обрезаем края кнопки. Мы даем кнопке 10% передышки. Это потому, что нам нужно учитывать тег «R25» и тот факт, что глючный эффект выходит за пределы кнопки. Это ловкий трюк с clip-path. Мы можем использовать его как контролируемый overflow: hidden. Мы говорим: «Да, вы можете немного переполниться. Но только столько».

Добавление этого к нашей кнопке дает нам желаемый эффект обрезки.

Создание тега R25

Далее давайте создадим тег «R25». Здесь мы могли бы найти псевдоэлемент и использовать его contentсвойство. Собственно, так это и делается на сайте. Но есть кое-что, о чем следует помнить при таком подходе — тот факт, что программа чтения с экрана может его прочитать. То же самое касается фактического текста кнопки. Каждая кнопка на сайте имеет текст, за которым следует символ подчеркивания. Хотели бы мы, чтобы это было прочитано программой чтения с экрана? Если да, то можем оставить как есть. Предположим, они предназначены для декоративных целей. Мы можем обновить нашу разметку и использовать aria-hiddenтак, чтобы средство чтения с экрана читало только текст кнопки:

 

aria-hidden class="cybr-btn__tag">R25

 

Чтобы стилизовать тег, мы можем указать его absoluteпозиционирование. Это требует от нас установки relativeпозиционирования на кнопке. Как и сама кнопка, тег использует inset box-shadow:

.cybr-btn {

label-size: 9px;

shadow-secondary-hue: 60;

shadow-secondary: hsl (var (—shadow-secondary-hue), 90%, 60%) ;

position: relative;

}

.cybr-btn__tag {

position: absolute;

padding: 1px 4px;

letter-spacing: 1px;

line-height: 1;

bottom: -5%;

right: 5%;

background: var (—shadow-secondary) ;

color: hsl (0, 0%, 0%) ;

font-size: var (—label-size) ;

box-shadow: 2px 0 inset var (—shadow-primary) ;

}

Здесь мы ввели еще несколько переменных CSS. Хотя они используются тегом, мы разместили их под селектором кнопок. На это есть причина. Позже мы можем решить использовать возможности переменных с областью действия. Если мы это сделаем, нам нужно только установить переменные на селекторе кнопок. Если бы мы оставили переменные под правилом тегов, переменные, установленные на кнопке, не имели бы власти над нижней областью действия. Мы устанавливаем a background-colorдля тега. Но вскоре становится очевидным, что это не делается на сайте.

Теперь, когда наш тег на месте, кнопка обретает форму.

Добавление эффекта глюка

Настало время глитч-эффекта. Исходя из опыта, я предположил, что кнопка дублируется. К продублированной кнопке будет применена некоторая форма обтравочной анимации. Нашей первой задачей здесь будет создание тела глюка. Помните, ранее мы открыли для себя использование фонового изображения? Вскоре стало ясно, почему это использовалось: чтобы сделать вырез для бирки. Это означает, background-colorчто кнопка за кнопкой одинакова для тега. Вырезанный угол также создается вместе с изображением.

красный фон

Обратите внимание, как синяя рамка следует за углом и огибает «R25»? Использование контура обрезки, как у нас, обрезает этот угол и не обрисовывает «R25». Реализация сайта использует файл drop-shadow.

Использование фонового изображения позволит нам воссоздать эффект. Однако это сопряжено с некоторыми компромиссами, если мы хотим сделать наши кнопки гибкими и многоразовыми.

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

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

Давайте продолжим с этим решением. Мы можем добавить новый элемент для глюка. Для этого нужен тот же текст, что и для нашей кнопки, а также его нужно скрыть от программы чтения с экрана с помощью aria-hidden:

 

aria-hidden class="cybr-btn__glitch">Glitch_

aria-hidden class="cybr-btn__tag">R25

 

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

.cybr-btn__glitch {

position: absolute;

height: 100%;

width: 100%;

top: 0;

left: 0;

box-shadow: 0 0 0 4px var (—shadow-primary) ;

text-shadow: 2px 2px var (—shadow-primary), -2px -2px var (—shadow-secondary) ;

}

Применение некоторых стилей, таких как text-shadowиbox-shadowполучить нас здесь.

Но нас не устраивает эта обрезка углов. Кроме того, то, как мы используем clip-path, чтобы дать передышку, кажется хрупким. Мы могли бы вернуть его с помощью небольшой хитрости. Если мы будем использовать псевдоэлементы для окрашивания кнопки, нам не придется обрезать всю кнопку! Мы могли бы использовать абсолютное позиционирование, а затем обрезать только псевдоэлементы. Нам также не нужно будет предоставлять передышку. Бонус здесь также в том, что у нас уже есть цвета кнопок в переменных:

.cybr-btn {

—clip: polygon (0 0, 100% 0, 100% 100%, 8% 100%, 0 70%) ;

}

.cybr-btn: before {

content: '';

position: absolute;

top: 0;

left: 0;

right: 0;

bottom: 0;

background: var (—primary) ;

clip-path: var (—clip) ;

z-index: -1;

}

Мы можем удалить clip-pathиз кнопки и поместить этот клип в переменную, которую мы можем использовать повторно. Нам нужно применить z-index: -1к псевдоэлементам, чтобы текст все еще отображался:

.cybr-btn {

—border: 4px;

}

.cybr-btn__glitch {

position: absolute;

top: calc (var (—border) * -1) ;

left: calc (var (—border) * -1) ;

right: calc (var (—border) * -1) ;

bottom: calc (var (—border) * -1) ;

background: var (—shadow-primary) ;

text-shadow: 2px 2px var (—shadow-primary), -2px -2px var (—shadow-secondary) ;

clip-path: var (—clip) ;

}

.cybr-btn__glitch: before {

content: '';

position: absolute;

top: calc (var (—border) * 1) ;

right: calc (var (—border) * 1) ;

bottom: calc (var (—border) * 1) ;

left: calc (var (—border) * 1) ;

clip-path: var (—clip) ;

background: var (—primary) ;

z-index: -1;

}

Затем мы можем повторно использовать клип для псевдоэлемента элемента сбоя. Хитрость в том, чтобы получить правильный элемент сбоя, состоит в том, чтобы расположить его абсолютно так, как если бы он был границей. Затем наложите псевдоэлемент поверх него. Применение одного и того же клипа к обоим элементам даст нам аккуратную синюю рамку, которая следует за углом.

Насколько это прикольно? Мы даже можем отрегулировать траекторию клипа, чтобы получить этот вырез вокруг «R25». Настройте clip-pathи удалите стили тегов следующим образом:

.cybr-btn {

—clip: polygon (0 0, 100% 0, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 70%) ;

}

.cybr-btn__tag {

position: absolute;

padding: 1px 4px;

letter-spacing: 1px;

line-height: 1;

bottom: -5%;

right: 5%;

color: hsl (0, 0%, 0%) ;

font-size: var (—label-size) ;

}

И здесь у нас есть возможность сделать что-то еще крутое. Когда я исследовал кнопку и обнаружил фоновое изображение, я потянул ее вниз. И я обнаружил, что граница возможна путем сложения двух изображений и перевода нижнего. Теперь, когда мы используем clip-path, мы можем сделать то же самое.

Два сложенных изображения

Если мы используем: beforeпсевдоэлемент для синего цвета нашей кнопки и: afterдля красного, а затем переведем: beforeпсевдоэлемент на размер границы, это даст нам границу. Это дает нам границу без применения border:

.cybr-btn: after,

.cybr-btn: before {

content: '';

position: absolute;

top: 0;

left: 0;

right: 0;

bottom: 0;

clip-path: var (—clip) ;

z-index: -1;

}

.cybr-btn: before {

background: var (—shadow-primary) ;

transform: translate (var (—border), 0) ;

}

.cybr-btn: after {

background: var (—primary) ;

}

Теперь у нас есть тень для тега и кнопки. И тег будет использовать фоновый цвет позади него. Попробуйте изменить на background-colorи bodyвы увидите!

Анимация кнопки

Почти готово! Подождите. У нас есть сбой. У нас есть все, что нам нужно. Осталось только анимировать кнопку на: hover.

Как происходит этот эффект глюка? Хитрость заключается в том, чтобы показать только элемент сбоя: hoverи по умолчанию применить к нему анимацию. Моим предположением здесь было использование transformи clip-pathв наборе ключевых кадров. И я был прав! Как я узнал? Я проверил кнопку и использовал «принудительное состояние» Chrome, чтобы установить кнопку в: hoverсостояние.

Форсирование состояния в инспекторе

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

Исходный код анимации

Это позволило мне увидеть используемые ключевые кадры:

@keyframes glitch-anim-1 {

0% {

opacity: 1;

-webkit-transform: translateZ (0) ;

transform: translateZ (0) ;

-webkit-clip-path: polygon (0 2%, 100% 2%, 100% 5%, 0 5%) ;

clip-path: polygon (0 2%, 100% 2%, 100% 5%, 0 5%)

}

2% {

-webkit-clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

-webkit-transform: translate (-5px) ;

transform: translate (-5px)

}

6% {

-webkit-clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

-webkit-transform: translate (5px) ;

transform: translate (5px)

}

8% {

-webkit-clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

-webkit-transform: translate (-5px) ;

transform: translate (-5px)

}

9% {

-webkit-clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

clip-path: polygon (0 78%, 100% 78%, 100% 100%, 0 100%) ;

-webkit-transform: translate (0) ;

transform: translate (0)

}

10% {

-webkit-clip-path: polygon (0 54%, 100% 54%, 100% 44%, 0 44%) ;

clip-path: polygon (0 54%, 100% 54%, 100% 44%, 0 44%) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

13% {

-webkit-clip-path: polygon (0 54%, 100% 54%, 100% 44%, 0 44%) ;

clip-path: polygon (0 54%, 100% 54%, 100% 44%, 0 44%) ;

-webkit-transform: translateZ (0) ;

transform: translateZ (0)

}

13.1% {

-webkit-clip-path: polygon (0 0,0 0,0 0,0 0) ;

clip-path: polygon (0 0,0 0,0 0,0 0) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

15% {

-webkit-clip-path: polygon (0 60%, 100% 60%, 100% 40%, 0 40%) ;

clip-path: polygon (0 60%, 100% 60%, 100% 40%, 0 40%) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

20% {

-webkit-clip-path: polygon (0 60%, 100% 60%, 100% 40%, 0 40%) ;

clip-path: polygon (0 60%, 100% 60%, 100% 40%, 0 40%) ;

-webkit-transform: translate3d (-5px, 0,0) ;

transform: translate3d (-5px, 0,0)

}

20.1% {

-webkit-clip-path: polygon (0 0,0 0,0 0,0 0) ;

clip-path: polygon (0 0,0 0,0 0,0 0) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

25% {

-webkit-clip-path: polygon (0 85%, 100% 85%, 100% 40%, 0 40%) ;

clip-path: polygon (0 85%, 100% 85%, 100% 40%, 0 40%) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

30% {

-webkit-clip-path: polygon (0 85%, 100% 85%, 100% 40%, 0 40%) ;

clip-path: polygon (0 85%, 100% 85%, 100% 40%, 0 40%) ;

-webkit-transform: translate3d (-5px, 0,0) ;

transform: translate3d (-5px, 0,0)

}

30.1% {

-webkit-clip-path: polygon (0 0,0 0,0 0,0 0) ;

clip-path: polygon (0 0,0 0,0 0,0 0)

}

35% {

-webkit-clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

-webkit-transform: translate (-5px) ;

transform: translate (-5px)

}

40% {

-webkit-clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

-webkit-transform: translate (5px) ;

transform: translate (5px)

}

45% {

-webkit-clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

-webkit-transform: translate (-5px) ;

transform: translate (-5px)

}

50% {

-webkit-clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

clip-path: polygon (0 63%, 100% 63%, 100% 80%, 0 80%) ;

-webkit-transform: translate (0) ;

transform: translate (0)

}

55% {

-webkit-clip-path: polygon (0 10%, 100% 10%, 100% 0,0 0) ;

clip-path: polygon (0 10%, 100% 10%, 100% 0,0 0) ;

-webkit-transform: translate3d (5px, 0,0) ;

transform: translate3d (5px, 0,0)

}

60% {

-webkit-clip-path: polygon (0 10%, 100% 10%, 100% 0,0 0) ;

clip-path: polygon (0 10%, 100% 10%, 100% 0,0 0) ;

-webkit-transform: translateZ (0) ;

transform: translateZ (0) ;

opacity: 1

}

60.1% {

-webkit-clip-path: polygon (0 0,0 0,0 0,0 0) ;

clip-path: polygon (0 0,0 0,0 0,0 0) ;

opacity: 1

}

to {

-webkit-clip-path: polygon (0 0,0 0,0 0,0 0) ;

clip-path: polygon (0 0,0 0,0 0,0 0) ;

opacity: 1

}

}

Для нашей анимации мы можем следовать той же структуре. Но в нашем примере мы можем применить разные версии пути клипа:

.cybr-btn {

shimmy-distance: 5;

clip-one: polygon (0 2%, 100% 2%, 100% 95%, 95% 95%, 95% 90%, 85% 90%, 85% 95%, 8% 95%, 0 70%) ;

clip-two: polygon (0 78%, 100% 78%, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 78%) ;

clip-three: polygon (0 44%, 100% 44%, 100% 54%, 95% 54%, 95% 54%, 85% 54%, 85% 54%, 8% 54%, 0 54%) ;

clip-four: polygon (0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0) ;

clip-five: polygon (0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0) ;

clip-six: polygon (0 40%, 100% 40%, 100% 85%, 95% 85%, 95% 85%, 85% 85%, 85% 85%, 8% 85%, 0 70%) ;

clip-seven: polygon (0 63%, 100% 63%, 100% 80%, 95% 80%, 95% 80%, 85% 80%, 85% 80%, 8% 80%, 0 70%) ;

}

@keyframes glitch {

0% {

clip-path: var (—clip-one) ;

}

2%, 8% {

clip-path: var (—clip-two) ;

transform: translate (calc (var (—shimmy-distance) * -1%), 0) ;

}

6% {

clip-path: var (—clip-two) ;

transform: translate (calc (var (—shimmy-distance) * 1%), 0) ;

}

9% {

clip-path: var (—clip-two) ;

transform: translate (0, 0) ;

}

10% {

clip-path: var (—clip-three) ;

transform: translate (calc (var (—shimmy-distance) * 1%), 0) ;

}

13% {

clip-path: var (—clip-three) ;

transform: translate (0, 0) ;

}

14%, 21% {

clip-path: var (—clip-four) ;

transform: translate (calc (var (—shimmy-distance) * 1%), 0) ;

}

25% {

clip-path: var (—clip-five) ;

transform: translate (calc (var (—shimmy-distance) * 1%), 0) ;

}

30% {

clip-path: var (—clip-five) ;

transform: translate (calc (var (—shimmy-distance) * -1%), 0) ;

}

35%, 45% {

clip-path: var (—clip-six) ;

transform: translate (calc (var (—shimmy-distance) * -1%));

}

40% {

clip-path: var (—clip-six) ;

transform: translate (calc (var (—shimmy-distance) * 1%));

}

50% {

clip-path: var (—clip-six) ;

transform: translate (0, 0) ;

}

55% {

clip-path: var (—clip-seven) ;

transform: translate (calc (var (—shimmy-distance) * 1%), 0) ;

}

60% {

clip-path: var (—clip-seven) ;

transform: translate (0, 0) ;

}

31%, 61%, 100% {

clip-path: var (—clip-four) ;

}

}

Это самая сложная часть для понимания. Что здесь происходит на самом деле? Наши ключевые кадры анимируют траекторию клипа на элементе сбоя. При этом покачиваем элемент из стороны в сторону. Мы можем замедлить анимацию, чтобы увидеть, что происходит.

Вот замедленная демонстрация, показывающая, как работает анимация:

И я также собрал демонстрацию, которая показывает различные состояния клипа:

Это значительно облегчило бы нам поддержку и настройку различных состояний анимации.

Соединение с наведением

Все, что осталось сделать, это привязать это к: hoverселектору. По умолчанию мы скрываем элемент глюка. Затем при наведении мы показываем его анимацию:

.cybr-btn__glitch {

display: none;

}

.cybr-btn: hover.cybr-btn__glitch {

display: block;

}

И это дает нам результат, который мы искали:

Заворачивать

И вот как вы воссоздаете кнопки Cyberpunk 2077, используя только CSS!

Помните, как мы использовали переменные для цветов? На то была причина. Комбинируя HSL с переменными, мы можем не только легко добавлять варианты цвета, но также можем добавить: activeизменение цвета.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая