Saltar al contenido principal

Te enseño cómo he creado una landing para Óxido Nitroso, mi proyecto de música techno

He decidido crearme un a.k.a. (as knonw as) de techno, Óxido Nitroso. La idea es ir subiendo canciones producidas por mí y alguna que otra sesión pinchando.

https://diegologs.com/notas/16618739400000

Contexto

Para promocionar mis canciones se me ha ocurrido montar una landing page para que la gente se pueda bajar los temas de forma gratuita (pidiendo un follow a cambio en Instagram).

De momento he aprovechado esta misma web y dominio para no complicarme la vida. Mi web actualmente está creada con Eleventy, por lo que la idea sería montar un layout completamente nuevo para el proyecto.

La estética general del proyecto va a ser con colores oscuros, verdes fosforitos, coches, efectos vhs, vintage, distorsión, etc, y todo ello quiero que quede reflejado en la web.

Aquí la web con el resultado final, espero que te mole:

🧯 https://diegologs.com/oxido-nitroso/hello-world

Landing the la página de óxido nitroso, se aprecia las letras óxido nitroso en grande, debajo el texto de hello world y debajo el logo de la botella de nitro, toda la imagen tiene un efecto vhs y de granulado

Manos a la obra

Bien, lo primero que hice fue crear un layout nuevo de Eleventy. Un layout en Eleventy sirve para definir la estructura que va a tener cada una de las páginas que use dicho layout. Voy a crear un fichero markdown por cada canción, con un link para poder descargar el tema y una preview embebida de Youtube.

El layout es tal que así:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="icon" href="/assets/images/oxidonitroso.png" />

    {% if seo_title %}
      <title>{{ seo_title }}</title>
      <meta property="og:title" content="{{ seo_title }}" />
    {% elif title %}
      <title>{{ title }}</title>
      <meta property="og:title" content="{{ title }}" />
    {% else %}
      <title>{{ site.title }}</title>
      <meta property="og:title" content="{{ site.title }}" />
    {% endif %}

    <meta name="theme-color" content="#00ff04" />

    <meta name="keywords" content="oxido nitroso, oxido nitroso dj, oxido nitroso music, music, techno, music production, free music">

    <meta property="og:site_name" content="Óxido nitroso" />
    <meta property="article:author" content="Óxido nitroso" />
    <meta name="author" content="Óxido nitroso">
    <meta name="fediverse:creator" content="@diegologs@mastodon.social" />

    {% if description %}
      <meta name="description" content="{{ description }}" />
      <meta property="og:description" content="{{ description }}" />
    {% elif date %} 
      <meta name="description" content="{{ date | customDate }}" />
      <meta property="og:description" content="{{ date | customDate }}" />
    {% else %} 
      <meta name="description" content="{{ site.description }}" />
      <meta property="og:description" content="{{ site.description }}" />
    {% endif %}

    <meta property="og:type" content="article">

    <meta
      property="og:image"
      content="{{ site.url }}/assets/images/oxidonitroso-bg.png"
    />

    <meta property="article:published_time" content="{{ date }}" />

    <link rel="canonical" href="{{ site.url }}{{ page.url }}" />
    <link rel="manifest" href="{{ site.url }}/manifest.json" />

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">

    {% include "css/oxidonitroso.njk" %}
  </head>

  <body>
    <main class="main">
      <div class="main-content">
        <h1 class="title">Óxido nitroso</h1>
        <h2 class="track-title">{{ title }}</h2>

        <img class="logo" src="/assets/images/oxidonitroso.png" alt="Óxido nitroso" />

        <div class="track-container">
          <div class="track">
            <div class="content">
              {{ content | safe }}
            </div>
          </div>
        </div>

         <footer>
          <a href="https://www.instagram.com/oxidonitrosodj/">Follow me on instagram</a>
          <a href="https://www.youtube.com/@oxidonitrosodj">Follow me on Youtube</a>
        </footer>

      </div>
    </main>
  </body>
</html>

Yo creo que el código se entiende aunque nunca hayas tocado Eleventy. Simplemente se crea la estructura HTML que van a tener las páginas de cada canción, usando la sintaxis de llaves para colocar la información de cada track. La parte de content | safe es donde se colocará el contenido markdown que escribiré para cada tema.

Ahora simplemente creo el fichero markdown:

___
layout: oxidonitroso
title: Hello world
description: Hello world is muy first production of the project Óxido nitroso. A simple, groovy track to warm up
youtubeUrl: https://youtu.be/v8fJhzocF-s?si=vRPkm4i6Uf-I-HGw
download: https://www.mediafire.com/file/hafqhtthxgchvzp/%25C3%2593xido_nitroso_-_Hello_world.zip/file
___

**Hello world** is my first track with Óxido nitroso project. The track is a standart **hardgroove techno**, with 140bpm, and nothing more. I'm highly influence with 90s tribal techno so this track is the result of that.


The idea is very simple, I only generate a simple bass with a techno kick and some hats as starting point. Then I saw a very funny tiktok with a guy screaming and laughing at the same time, so I sample it and generate the song aroung that simple idea. 

The structure of the tracks is simple too, only taking in a out elements until the track finish. just a simple track, nothing more.

Welcome to Óxido nitroso project.

Remember, this techno track is free to download, no credit cat, registration needed. 

La parte de layout es donde indico que se use el layout que he creado antes. Ya solo queda crear la colección en eleventy:

eleventyConfig.addCollection("oxidonitroso", function (collectionApi) {
	return collectionApi.getFilteredByGlob("src/oxidonitroso/*.md").reverse();
});

Vamos con el embed del vídeo de Youtube, para ello me he creado una utilidad en Eleventy, en su config:

  eleventyConfig.addNunjucksShortcode("youtube", function(videoURL) {
    let id = "";
    try {
      const url = new URL(videoURL);
      if (url.hostname.includes("youtu.be")) {
        id = url.pathname.slice(1);
      } else {
        id = url.searchParams.get("v");
      }
    } catch (e) {
      id = videoURL;
    }

    return `
      <div class="video-responsive">
        <iframe 
          src="https://www.youtube-nocookie.com/embed/${id}" 
          frameborder="0" 
          allowfullscreen
          title="YouTube video"></iframe>
      </div>
    `;
  });

En el CSS he metido estos estilos para hacer que el vídeo nunca se pase del ancho máximo de la página y siempre mantenga la proporción:

.video-responsive {
	display: block;
	max-width: 900px;
	width: 100%;
	margin-left: auto;
	margin-right: auto;
	aspect-ratio: 16 / 9;
	background: #000; 
	position: relative;
}

.video-responsive iframe {
	width: 100%;
	height: 100%;
	border: 0;
	display: block;
	position: absolute;
	top: 0;
	left: 0;
}

La parte del follow en Instagram

El tema de Instagram es que creo que no permiten detectar el follow en las cuentas (o está prohibido) por lo que he tomado la decisión de permitir que se pueda descargar el tema aunque no le den a follow.

Simplemente detecto cuándo se pulsa el link para cambiar el texto del botón a "Free download".

Una cosa interesante es que he usando la etiqueta template de HTML para generar contenido "oculto". Cuando el usuario pulsa sobre el botón de descarga de tema, con JS, se clona el contenido del template y se añade a la página visible, ocultando al mismo tiempo el resto de contenido.

<template id="insta">
	<div class="content">
		<p class="follow-title">But first <strong>follow me on instagram</strong>:</p>
		<div class="insta">
			<img class="insta-photo" src="/assets/images/oxidonitroso-avatar.jpg" alt="Óxido nitroso profile pic avatar" />
			<div>
				<p class="insta-name">Óxido nitroso</p>
				<p class="insta-handle">@oxidonitrosodj</p>
			</div>
			<a class="insta-button" id="insta-button" href="https://instagram.com/oxidonitrosodj">Follow</a>
		</div>
		<div class="insta-content">
			<p>Yes, you can still download the track <strong>without following me</strong>, but come on, the track has cost me effort, at least follow me on Instagram.</p>
			<p>And, with the follow you get: <strong> +30 charisma, +20 nitrous, +10 rep and +69 beaty.</strong><p/>
		</div>
			<a class="button download disabled" id="download-track" href="{{ download }}">Just download, without follow 😭</a>
	</div>
</template>

Al pulsar sobre el enlace activo el siguiente código con JS:

const template = document.getElementById('insta');
const templateContent = template.content.cloneNode(true);

document.querySelector('.track-container').appendChild(templateContent);

Efectos especiales

Vamos con la parte de los efectos visuales, para darle un toque de personalidad a la web. Como digo voy a intentar generar un efecto VHS chulo, con algunos glitches como de pérdida de señal.

Lo primero que hice fue meter un efecto de flare (luz) encima del vídeo, para ello tiro de los pseudoelementos CSS:

  .video-responsive::before {
    content: "";
    position: absolute;
    top: -18%;
    left: 0;
    width: 100%;
    height: 100%;
    background: url("/assets/images/flare.jpg");
    mix-blend-mode: difference;
    background-position: contain;
    background-size: contain;
    background-repeat: no-repeat;
    filter: hue-rotate(90deg) brightness(0.7);
    z-index: -2;
  }

Se aprecia un efecto como de luz saliendo detrás del vídeo

La parte clave es el mix-blend-mode, ya que permite mezclar mejor la imagen con el propio contenido, haciendo que se mezcle mejor. Además he modificado el color (para que sea verde) del flare usando hue-rotate en la propiedad filter.

Seguí con la parte del efecto VHS sobre toda la página, para ello me descargué un loop de efecto VHS y lo aplique también sobre un pseudoelemento HTML (intento usarlos al máximo porque este tipo de efectos son decorativos y no hace falta crear más etiquetas HTML)

  .main:after {
    content: "";
    background-image: url("https://64.media.tumblr.com/da60c13b478dda09ab90c27e880983b8/tumblr_nd4pwdPKdc1tun3l0o1_1280.gifv");
    mix-blend-mode: color-dodge;
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    pointer-events: none;
  }

Importante la parte de pointer-events a none para que la página siga siendo interactuable aunque tenga un elemento por encima. Otra vez usando mix-blend-mode para que se integre bien con la página.

Para el título principal más de lo mismo, tirar de pseudoelementos, en este caso para generar un texto de color cyan y otro morado para generar efecto como de distorsión 3D de colores.

.title:after, .title:before {
	content: attr(data-text);
	position: absolute;
	mix-blend-mode: screen;
}

.title:after {
	top: 0;
	left: 6px;
	color: cyan;
	z-index: -1;
	animation: move 1.5s infinite;
}

.title:before {
	top: -3.4px;
	left: -3.6px;
	color: purple;
	z-index: -2;
	filter: blur(1px);
}

Cada texto está ligeramente desplazado para dar el efecto. Aquí lo interesante es el tema de content: attr(data-text);, que lo que hace es pillar el data-text de la propia etiqueta HTML para que el contenido de los pseudoelementos sea el propio texto del DOM:

<h1 class="title" data-text="Óxido nitroso">Óxido nitroso</h1>

Por último decir que también he aplicado un pequeño efecto blur de desenfocado de 1px para dar efecto de mala calidad, nuevamente buscando el toque de VHS.

Letras de óxido nitroso con el efecto de color cyan/red de distorsión

También tiene una pequeña animación que desplaza ligeramente el texto, simulando efecto de pérdida de señal:

  @keyframes move {
    0% {
      transform: none;
    }
    30% {
      transform: none;
    }
    31% {
      transform: translateX(-6px);
    }
    33% {
      transform: none;
    }
    98% {
      transform: none;
    }
    100% {
      transform: translateX(6px);
    }
  }

Vamos ahora con el título del nombre del track, aquí a parte de meter también un pequeño desenfoque de 1px, le he metido un efecto de glitch, para ello he creado una animación que mediante unos recortes y desplazamientos simula un efecto de distorsión de señal:

  .glitch {
    position: relative;
  }

  .glitch::before,
  .glitch::after {
    -webkit-animation-iteration-count: infinite;
    animation-iteration-count: infinite;
    -webkit-animation-timing-function: linear;
    animation-timing-function: linear;
    animation-direction: alternate-reverse;
    overflow: hidden;
    position: absolute;
    top: 0;
    clip: rect(0, 900px, 0, 0);
    content: attr(data-text);
  }

  .glitch::after {
    -webkit-animation-name: glitch-animation;
    animation-name: glitch-animation;
    animation-duration: 10s;
    left: 4px;
    text-shadow: -1px 0 #ffa800;
    background: black;
  }

  .glitch::before {
    -webkit-animation-name: glitch-animation-2;
    animation-name: glitch-animation-2;
    animation-duration: 20s;
    left: -4px;
    text-shadow: 1px 0 #00d8ff;
    background: black;
  }

  @keyframes glitch-animation {
    0% {
      clip: rect(42px, 9999px, 44px, 0);
    }
    5% {
      clip: rect(12px, 9999px, 59px, 0);
    }
    10% {
      clip: rect(48px, 9999px, 29px, 0);
    }
    15.0% {
      clip: rect(42px, 9999px, 73px, 0);
    }
    20% {
      clip: rect(63px, 9999px, 27px, 0);
    }
    25% {
      clip: rect(34px, 9999px, 55px, 0);
    }
    30.0% {
      clip: rect(86px, 9999px, 73px, 0);
    }
    35% {
      clip: rect(20px, 9999px, 20px, 0);
    }
    40% {
      clip: rect(26px, 9999px, 60px, 0);
    }
    45% {
      clip: rect(25px, 9999px, 66px, 0);
    }
    50% {
      clip: rect(57px, 9999px, 98px, 0);
    }
    55.0% {
      clip: rect(5px, 9999px, 46px, 0);
    }
    60.0% {
      clip: rect(82px, 9999px, 31px, 0);
    }
    65% {
      clip: rect(54px, 9999px, 27px, 0);
    }
    70% {
      clip: rect(28px, 9999px, 99px, 0);
    }
    75% {
      clip: rect(45px, 9999px, 69px, 0);
    }
    80% {
      clip: rect(23px, 9999px, 85px, 0);
    }
    85.0% {
      clip: rect(54px, 9999px, 84px, 0);
    }
    90% {
      clip: rect(45px, 9999px, 47px, 0);
    }
    95% {
      clip: rect(37px, 9999px, 20px, 0);
    }
    100% {
      clip: rect(4px, 9999px, 91px, 0);
    }
  }
  @keyframes glitch-animation-2 {
    0% {
      clip: rect(65px, 9999px, 100px, 0);
    }
    5% {
      clip: rect(52px, 9999px, 74px, 0);
    }
    10% {
      clip: rect(79px, 9999px, 85px, 0);
    }
    15.0% {
      clip: rect(75px, 9999px, 5px, 0);
    }
    20% {
      clip: rect(67px, 9999px, 61px, 0);
    }
    25% {
      clip: rect(14px, 9999px, 79px, 0);
    }
    30.0% {
      clip: rect(1px, 9999px, 66px, 0);
    }
    35% {
      clip: rect(86px, 9999px, 30px, 0);
    }
    40% {
      clip: rect(23px, 9999px, 98px, 0);
    }
    45% {
      clip: rect(85px, 9999px, 72px, 0);
    }
    50% {
      clip: rect(71px, 9999px, 75px, 0);
    }
    55.0% {
      clip: rect(2px, 9999px, 48px, 0);
    }
    60.0% {
      clip: rect(30px, 9999px, 16px, 0);
    }
    65% {
      clip: rect(59px, 9999px, 50px, 0);
    }
    70% {
      clip: rect(41px, 9999px, 62px, 0);
    }
    75% {
      clip: rect(2px, 9999px, 82px, 0);
    }
    80% {
      clip: rect(47px, 9999px, 73px, 0);
    }
    85.0% {
      clip: rect(3px, 9999px, 27px, 0);
    }
    90% {
      clip: rect(26px, 9999px, 55px, 0);
    }
    95% {
      clip: rect(42px, 9999px, 97px, 0);
    }
    100% {
      clip: rect(38px, 9999px, 49px, 0);
    }
  }

He usado la misma técnica de antes de coger el atributo data-text del HTML para que los pseudoelementos tengan el propio texto en su contenido.

Vamos por último con el efecto que simula scanlines. Para hacer el efecto generé una clase scanlines y aplique estos estilos:

  .scanlines {
    position: relative;
  }

  .scanlines::after {
    content: "";
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2147483648;
    background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.3) 51%);
    background-size: auto;
    background-size: 100% 4px;
    pointer-events: none;
  }

Simplemente se genera el efecto de líneas horizontales usando un linear-gradient con un tamaño de 4px para que el patrón de líneas se repita todo el rato. El efecto podría tener movimiento, pero de momento lo he dejado así.

Por último hay unas letras en vertical que simplemente se han colocado con CSS usando un transform para rotarlas. Además he colocado el logo de fondo muy desenfocado simulando que está "lejos". También hay un cuadrado azul a la derecha, también con un mix-blend-mode.

Conclusiones

Como ves la web es muy muy sencilla, un par de botones, el vídeo y el título. La idea era darle un toque estético muy llamativo y yo creo que lo he conseguido.

De este artículo puedes sacar el uso de mix-blend-mode, una propiedad CSS que uso muchísimo en toda mi web, sobre todo para texturas.

También que con Eleventy te puedes montar un proyecto en muy poco tiempo (si sabes algo de HTML, CSS y Javascript), en mi blog tienes un artículo llamado Cómo crear un blog con ficheros markdown para que te lo montes gratis.

Y poco más, simplemente me queda recordar las redes sociales de Óxido nitroso:

Diego López Permalink

Increíble, creo que después de muchos meses estoy cerca de terminar una canción. Como siempre lo que más me está costando es hacer el break. Incluso siendo techno no es tan fácil como quitar elementos y ya, hay que conseguir que no suene muy vacío y que a la vez cree tensión para el buildup.

Otra cosa que me cuesta es la estructura, hacer que el tema avance sin sonar repetitivo. Suelo añadir y quitar elementos, crear automatizaciones, fx y demás historias, pero no consigo que me guste, se nota que me falta experiencia.