Categorias
Geral

Utilizando Dark Mode em sua aplicações web

Nesse post vamos ver como implementar Dark mode em nossas aplicações web, lançado em sistemas operacionais tanto Mobile quanto para Desktop agora podemos controlar a estilização do nosso conteúdo de acordo com a preferência do usuário. Falando um pouco dessa trajetória no inicio da computação por questões de limitação utilizávamos “dark mode” com os famosos monitores de tela preta como o texto verde.

Com a evolução das telas tivemos uma mudança dos sistemas operacionais para representar elementos reais como documentos e images. Com o passar dos anos a comunidade de desenvolvimento passou a utilizar IDEs para codificação em dark, e agora nos últimos anos Dark Mode passou a ser uma opção para sistema operacionais mobile.

Preferências e vantagens

Particularmente por uma longa exposição durante o dia a diversas telas, no trabalho, celular e em casa. Eu utilizo modo como uma forma de descanso mas estudos realizados pelo time do android mostram que dark mode permite uma economia de até 60% em algumas aplicações, por exemplo, o Youtube app em dark mode.

Mas a web não fica para trás, nas últimas versões dos browsers passaram a reconhecer quando o usuário utiliza o modo, Conseguimos através de media queries monitorar a preferência do usuário.

Media Query CSS

@media (prefers-color-scheme: dark) { ... }

@media (prefers-color-scheme: light) { ... }

@media (prefers-color-scheme: no-preference) { ... }

Com essas três media queries conseguimos captar 3 estados: dark, light, no-preference. Assim conseguimos alterar propriedades quando o usuário utiliza um modo especifico, por exemplo:

body {
  color: #000;
  background-color: #fff;
}

@media (prefers-color-scheme: dark) { 
  body {
     color: #fff;
     background-color: #000;
  }
}

Habilitando Dark mode

No MacOS definimos através de configurações > preferencias gerais:

Alterando para Dark Mode Mac OS

Para iOS vamos em Configurações > Tela e Brilho

Como definir dark mode no iOS

Essa é uma maneira que podemos modificar toda a configuração do sistema operacional. Mas quando estamos desenvolvendo ficar trocando todas as configurações do sistema operacional pode se tornar inconveniente. Nas novas versões do Chrome 79 podemos simular essa mudança como o Chrome DevTools. Para isso vamos no seguinte painel.

Com o DeTools aberto clicamos no menu mais opções(três pontos) e habilitamos a opção renderização(rendering), Lá na propriedade “Emulate CSS media feature prefers-color-scheme” conseguimos simular light mode e dark mode.

Tela do devtools do Chrome

Agora que sabemos como simular e qual media query utilizamos, vamos aplicar o código ao projeto utilizado no curso de PWA. Atualmente o tema principal utilizar um background preto com fontes branca, agora vamos criar uma tema light com as cores invertidas:

Resultado final Light vs Dark mode

Controlando as cores com variáveis CSS

Para isso vamos fazer algumas modificações. Atualmente o projeto utiliza SASS, vou levar em consideração o modo light como o modo padrão e para esse exercício vou utilizar as variáveis para CSS para modifica-las quando usuário muda o modo:

//cores com variáveis CSS
:root {
  --primary-color: #fff;
  --secondary-color: #000;
  --ternary-color: #bae2fd;
  --gray-color: #333;
}

@media (prefers-color-scheme: dark) {
  :root {
    --primary-color: #000;
    --secondary-color: #fff;
  }
}

No código acima á parte de nossa solução, temos as variáveis sendo modificadas quando o usuário muda entre modos, assim não precisamos modificar todos os seletores do nosso CSS apenas as variáveis.

Quando testamos nossa aplicação em Dark Mode deparamos com a seguinte situação:

SVGs e images precisa de ajustes

Para esse caso temos duas correções os icones das redes sociais estão como “embedados” em nosso HTML conseguimos controlar a propriedade fill do nosso SVGs para isso adicionamos o seguinte código:

.social__icon {
    width: 25px;
    height: 25px;
    fill: var(--secondary-color);
}

a:hover .social__icon {
    fill: var(--primary-color);
}

Para a logo principal, por conta de ser uma image preto e branco conseguimos inverter sua cor através de filtros CSS:

@media (prefers-color-scheme: dark) {
  .header__picture {
    filter: invert(1);
  }
}

Assim temos o fix para os dois items, lembrando que no caso da imagem funcionou por que eu utilizo uma image monocromática e temos o seguinte resultado:

Observações

Implementações do dark mode pode ser mais complexa que no exemplo acima, por conta da paleta de cores restritas temos uma simples implementação, caso de uma paleta mais complexa preste atenção nos níveis de contraste.

Em caso mais complexos o ideal é dividir o CSS e carregar o estilo para mode preterido pelo usuário, adicionando as seguintes tags em nosso html:

<link rel="stylesheet" href="/dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/light.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)">
<!-- Estilo principal -->
<link rel="stylesheet" href="/style.css">

No caso acima temos um estilo geral style.css e dark.css para os usuários que preferem dark mode e light.css para os usuários sem seleção ou light mode. Assim os usuários em dark mode so carregam o css relacionado.

Para browser antigos

Caso o browser não suporte prefers-color-scheme conseguimos adicionar um fallback em nosso código:

<script>
  // Se `prefers-color-scheme` não for suportado pelo browser, o fall back será light mode.
  if (window.matchMedia('(prefers-color-scheme: dark)').media === 'not all') {
    document.documentElement.style.display = 'none';
    document.head.insertAdjacentHTML(
        'beforeend',
        '<link rel="stylesheet" href="/light.css" onload="document.documentElement.style.display = \'\'">'
    );
  }
</script>

Após a inclusão da media query a validação do stylelint acusou a media query como desconhecida, isso por conta da versão do style lint caso passe o mesmo problema atualize o stylelint para versão 10 ou superior e a validação passará a reconhecer a media query

Falso alert de validação das versões antigas do stylelint

Suporte

Atualmente Novembro de 2019 o suporte para a media query prefers-color-scheme é de 74% dos browser do mercado:

Edge, firefox, chrome, safari, opera, iOS safari suas ultimas versões suportam dark mode.

O código completo você encontra em meu github: https://github.com/fellyph/pwa-tutorial/tree/video/dark_mode_20_final

A base desse tutorial vem do projeto utilizado no curso de PWA que estou rodando no Youtube: https://blog.fellyph.com.br/curso-online-progressive-web-apps/

Qualquer dúvida deixe um comentário e até o próximo post.

Esse post foi baseado no artigo “Hello darkness, my old friend” do @tomayac: https://web.dev/prefers-color-scheme/

Categorias
AMP Accelerated mobile pages

Criando menu acordeão com AMP: amp-accordion

Um dos componentes mais populares do AMP é o amp-accordion, o componente de accordion(acordeão) ou sanfona como também é conhecido permite o usuários comprimir e expandir determinadas sessões. Ele é útil também em dispositivos móveis para reduzir a quantidade de scroll de uma sessão. Neste post iremos entrar em alguns detalhes desse componente.

Comportamento

Cada filho imediato do componente para ser considerado um accordion session precisa estar dentro de uma tag section.

  • amp-accordion pode conter uma ou mais elementos <section>
  • Cada <section> deve conter exatamente dois filhos.
  • O primeiro filho vai representar o header o ideal ele utilizar as tags h1, h2, h3, …, h6 e header.
  • Clique ou tap no header vai expandir ou comprimir o elemento responsável pelo conteúdo.
  • Os estados de expandir e comprimir é preservado por cada elemento.

Eventos e ações

Accordion component dispara dois eventos:

  • expand: quando o elemento section referente ao conteúdo muda do estado recolhido para o estado expandido.
  • collapse: quando a elemento section referente ao conteúdo muda do estado expandido para o estado recolhido.

Já quando falamos de ações temos as seguintes ações disponíveis: toggle, collapse e expand. Agora temos uma introdução sobre o componente vamos para um exemplo prático primeiro precisamos chamar o script responsável.

<script async custom-element="amp-accordion" src="https://cdn.ampproject.org/v0/amp-accordion-0.1.js"></script>

Nosso markup ficará da seguinte forma:

<amp-accordion expand-single-section>
              <section id="step1" expanded>
                <h3 class="recipe__step__title">1. Gather the ingredients.</h3>
                <p>
                  Lorem, ipsum dolor sit amet consectetur adipisicing elit.
                  Perspiciatis explicabo vero, facere, dolorem quia eligendi solut.
                </p>
              </section>
              <section id="step2">
                <h3 class="recipe__step__title">
                  2. Cut half of a lime into small wedges.
                </h3>
                <p>
                  Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic,
                  ipsam repellat? Animi excepturi nostrum ducimus minus natus nobis.
                </p>
              </section>
</amp-accordion>

E resultado será o seguinte:

No markup acima não coloquei as 6 sections mas o código é só uma repetição do primeiro bloco. Mas o importante nesse print são dois pontos primeiro as propriedades:

  • expand-single-section: com ela determinamos somente um sessão do acordeão será aberta por vez.
  • expanded: determina se a sessão iniciará expandida.

Segundo o componente já possui um estilo, caso queira personalizar esse estilo podemos adicionar qualquer class nos elementos que fazem parte do acordeão:

<amp-accordion expand-single-section>
              <section id="step1" expanded>
                <h3 class="recipe__step__title">1. Gather the ingredients.</h3>
                <p>
                  Lorem, ipsum dolor sit amet consectetur adipisicing elit.
                  Perspiciatis explicabo vero, facere, dolorem quia eligendi soluta
                  expedita maxime animi, quos deleniti exercitationem quae impedit
                  recusandae nobis laborum hic? Sed, dolor.
                </p>
              </section>
              <section id="step2">
                <h3 class="recipe__step__title">
                  2. Cut half of a lime into small wedges.
                </h3>
                <p>
                  Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic,
                  ipsam repellat? Animi excepturi nostrum ducimus minus natus nobis
                  porro, facere, ipsa id repudiandae illum tenetur nulla vel et
                  molestias fugit.
                </p>
              </section>
</amp-accordion>

css:

.recipe__step__title {
        background: var(--secondary-color);
        padding: 0.5em;
        border-top: transparent;
        border-left: transparent;
        border-right: transparent;
        border-bottom: 3px solid var(--primary-color);
        color: var(--header-color);
        text-align: left;
      }

Com isso temos o resultado:

O exemplo acima é somente para exibir como o podemos alterar o cabeçalho, por exemplo, alterando o h3 conseguimos personalizar o header da nossa sessão.

Para mais tutorias sobre amp confira a página de categoria: https://blog.fellyph.com.br/category/amp/

Para acompanhar o curso online assine o canal:

Link da playlist: https://www.youtube.com/watch?v=Ku9-Hnqjjb4&list=PLmIA3VZysEqQxsVcZ7u2ZHOnh78eIOKON

Categorias
AMP Accelerated mobile pages

Ouvindo eventos com AMP e trabalhando com amp-sidebar

Para esse post iremos implementar um menu slider, com isso precisar aprender dois itens importantes o primeiro é interação com eventos e o segundo é o componente amp-sidebar, para nosso exercício iremos aplicar em nosso projeto the best caipirinha o menu atualmente se encontra sempre visível no modo desktop não temos problemas mas nas versões mobile temos os menu sempre cobrindo o nosso conteúdo, como podemos ver na imagem abaixo:

nosso menu se comporta na versão mobile

Como solução para o nosso problema iremos exibir o menu apenas quando o usuário clicar no botão menu. Para isso precisamos acompanhar o evento de click do usuário. Como fazemos isso com AMP? Com JavaScript ouvimos os eventos através do addEventListener mas com AMP iremos utilizar o atributo on.

atributo “on”

Com AMP para acompanhar eventos com tap, cliques de mouse interações com o teclado. Esses eventos nos componentes AMP são acompanhados pelo atributo on além de acompanhar os eventos podemos definir ações específicas chamadas de actions, por exemplo:

<button on="tap:menu.hide">

No código acima temos um botão que está acompanhando a ação de tap e quando realizado o elemento com id “menu” onde será realizada a ação de “hide” para esse código funcionar precisamos de elemento com o id menu exemplo:

<div id="menu">Meu menu</div>
<button on="tap:menu.hide">Fechar o menu</button>

Ações

No exemplo acima utilizamos a ação hide ele faz parte de um grupo de ações genéricas disponíveis para todos componentes AMP e elementos HTML que são elas:

  • hide
  • show
  • toggleVisibility
  • toggleClass
  • scrollTo
  • focus

Alguns componentes possuem ações e eventos específicos, por exemplo, o amp-carousel possui a ação goToSlide onde podemos exibir um slide específico e o evento slideChange que com ele podemos acompanhar esse evento e determinar alguma ação quando necessário.

amp-sidebar

Agora que temos uma introdução a eventos e ações com AMP vamos implementar esses novos recursos a nossa aplicação, primeiramente vamos adicionar o component amp-sidebar em nossa aplicação ele irá necessitar um script extra:

<script async custom-element="amp-sidebar" src="https://cdn.ampproject.org/v0/amp-sidebar-0.1.js"></script>

O componente amp-sidebar oculta o conteúdo destinado a uma acesso temporário exemplo, links de navegação, botões, menus. O componente pode ser controlado por botões.

amp-sidebar possui um comportamento bem específico:

  • O conteúdo irá aparecer sempre da direita ou esquerda da página dependendo de qual valor definido pela propriedade side.
  • amp-sidebar deve conter um componente HTML válido.
  • layout nodisplay é obrigatório
  • É recomendado que o componente amp-sidebar seja um filho direto da tag body para preservar a ordem lógica do DOM especialmente para leitores de tela.
  • A ação de zoom é desabilitada para preservar o seu funcionamento.
  • A largura da sidebar pode ser ajustado via CSS mas respeitando uma largura mínima de 45px.

Depois da introdução ao componente vamos a sua utilização:

  <button class="recipe__header__menuBtn" on='tap:sidebar-menu.toggle'>Menu</button>    
  <amp-sidebar id="sidebar-menu" side="left" layout="nodisplay">
          <nav class="recipe__navigation nav">
            <ul class="recipe__navigation__menu">
              <li class="recipe__navigation__item">
                <a href="#about">About</a>
              </li>
              <li class="recipe__navigation__item">
                <a href="#ingredients">Ingredients</a>
              </li>
              <li class="recipe__navigation__item">
                <a href="#steps">Steps to Make It</a>
              </li>
            </ul>
          </nav>
        </amp-sidebar>

No código acima temos um botão responsável por invocar a exibição da nossa sidebar, inicialmente ela estará no estado nodisplay, acompanhamos o evento de tap e quando pressionado ele irá realizar ação de toggle em nosso amp-sidebar com id sidebar-menu, todo o efeito de animação já será definido pelo componente e depois que o código é aplicado em nossa aplicação o resultado será o seguinte:

Assim temos a solução para o nosso menu tanto para desktop quanto mobile

No código acima é uma parte da nossa solução para ver a solução completa só acessa o repositório no github vamos fechando esse tutorial por aqui. Caso tenha alguma dúvida só deixar um comentário.

Categorias
JavaScript

O que é MobileNet?

MobileNet é uma simplificação de redes neurais para possibilitar o seu uso em aplicações web, com isso podemos criar rapidamente uma aplicação de reconhecimento de images. Inicialmente com um propósito mobile-first MobileNet passou a ser utilizada pelo tensorflow.js e ml5.js

Em mais detalhes, MobileNets são uma classe de convolução de redes neurais projetadas por pesquisadores do Google. Eles foram inicialmente projetados para facilitar o usos de recursos de seu aparelho móvel onde parte da informação é processada na nuvem assim economizando recursos computacionais de seu celular.

Convolução???

Na convolução as imagens em pixels são convertidas em dados numéricos, por exemplo, a imagine que você possui 512 por 512 pixels abaixo:

Foto fellyph cintra

Nós seres humanos classificamos da seguinte forma:

  1. Homem feio
  2. Barba falha
  3. Camisa listrada
  4. Sentado numa cadeira preta

No exemplo acima fazemos a convolução da imagem de 512×512 em 4 dados. Da mesma forma com machine learning essa image será divida em pequenos recortes e esses recortes serão analisados por um grupo de pequenas rede neurais, após esse processo as imagens será traduzida em números (lista de arrays) que classificam essa imagem.

Esse processo é repetido para cara bloco por várias redes neurais até chegar um grupo de números que definem a imagem.

O nível de precisão de uma MobileNet é menor que as redes neurais tradicionais, para agilizar o tempo de processamento. mas isso é compensado pela quantidade de amostras que a MobileNet possui na nuvem.

Por padrão MobileNet utilizar o dataset ImageNet com mais de 1500.000 de imagens divididas em 1.000 categorias. Mas Google fez o código fonte MobileNet aberto com 16 pontos de verificação, com isso podemos treinar o nosso próprio modelo reutilizando a estrutura da mobileNet.

Processo de identificação com ml5.js por exemplo é bem simples, podemos passar o dataset a partir de uma webcam e as imagens pode ser para infinitas finalidades:

  • Objetos
  • Expressões faciais
  • Cores
  • Letras
  • Posições de um corpo humano

Para o modelo não importa a finalidade os objetos serão classificados em números. Assim precisamos das imagens e as labels para classifição dessas imagens

Após o treinamento podemos dizer através de uma porcentagem o que a imagem se trata.

No próximo post iremos falar mais sobre como utilizar o nosso próprio dataset com ml5.js, se ainda não conferiu o post sobre ml5.js aqui vai o link: https://blog.fellyph.com.br/javascript/introducao-ao-ml5-js/

Categorias
JavaScript Tutoriais

Machine Learning descomplicado com ML5.js

Conhecida como uma biblioteca amigável para machine learning, ml5.js é uma das alternativas mais acessíveis para você que deseja se aventurar no mundo do Machine Learning. Meses atrás eu descobri esta biblioteca web através do canal coding train.

O que é machine Learning?

Antes de entramos em detalhe se você nunca ouviu falar em Machine Learning, algo que entrou no hype na computação com uma serie de recursos possíveis graça a computação na nuvem e a infinidade de dados que possuímos atualmente. Mas machine learning existe na computação desde a década de 40.

Quando umas das definições mais clássicas foram criadas por Arthur Samuels:

É o campo de estudo onde são dados aos computadores a capacidade de aprender algo sem ser explicitamente programado.

Uma ótima definição feita no canal coding train com uma simples comparação na programação clássica temos o seguinte algoritmo:

if(mouseX > 200) {
  console.log("lado direito");
} else {
  console.log("lado esquerdo");
}

Já com machine learning passamos uma série de dados:

  • 300 – direita
  • 401 – direita
  • 210 – direita
  • 100 – esquerda
  • 87 – esquerda
  • 24 – esquerda

A partir dessa série de dados o Machine Learning vai analisar esses dados e tentar fazer uma previsão de dados futuros nesse caso a quantidade de amostra é importante para o sucesso da nossa aplicação.

Dentro dos tutoriais sobre ML5.js vamos falar muito sobre três items:

  1. Redes neurais – os algoritmos responsáveis por processar tarefas de Machine learning, o de temos uma entrada de dados e fazemos uma série de cálculos para ter uma saída esperada. Para identificar padrões específicos.
  2. modelos pré-treinados – modelos pre-treinados é a parte que iremos abordar 90% dos casos por que são os items em mais evidência do momento, utilizado pelas grandes empresas para identificar usuários, moderação de texto, reconhecimento de voz entre outras funcionalidades.
  3. data sets – são os grupos de dados que utilizamos para treinar os nossos modelos.

Baseado nessa rápida de alguns pontos chaves, agora vamos falar sobre ML5, a idéia é criar mais posts sobre o assunto assim quando alguns pontos dos Machine learning forem abordados traremos mais descrições

ML5.js

Ml5.js foi construída em cima do tensorFlow.js que possibilita o uso machine learning na web com o processamento de dados através da GPU, geralmente utilizado para o processamento gráfico, Mas o tensorFlow.js utiliza desse recurso para não afetar o processamento da CPU.

Com ml5.js visa atingir uma audiência que trata machine learning em mais alto nível desenvolvedores web e artistas. A biblioteca possui algoritmos e modelos que realizam a classificação de três tipos da dados imagem, sons e textos.

Para cada grupo temos umas série de funções com diferentes funcionalidades, por exemplo, a função imageClassifier() através de redes neurais conseguimos reconhecer o conteúdo da image retornando um objeto com informações sobre a imagem vindo de um modelo pre-treinado

Mas com a função imageClassifier podemos treinar o nosso próprio modelo. Outra função interessante é poseNet(), com ela conseguimos identificar poses humanas, ela pode identificar uma única pose ou múltiplos integrantes.

No geral temos 10 funções para classificação de images e 2 funções para classificação de som e 4 funções para identificação de texto.

Tensorflow.js

É uma biblioteca criada pelo Google para trabalhar machine learning no JavaScript, foi construído baseado no TensorFlow biblioteca escrita em Python. Com tensorFlow.js podemos utilizar modelos pré-treinados, para classificação de imagens, detecção de objetos, detecção de texto tóxico e reconhecimento de comandos por voz.

Ou treinar o nosso próprio modelo, ele é uma biblioteca base do ML5.js possui uma estrutura mais complexa que o ML5.js

Exemplo prático

Agora que temos uma introdução desses três items chaves vamos fazer o nosso primeiro exercício com ML5.js, quando escrevi esse tutorial a lib estava na versão 0.3.1, agora vamos escolher uma foto para testar com ml5.js, sinta-se a vontade para escolher a sua foto:

Escolhi essa imagem para realizar meu teste

Agora temos a nossa imagem vamos criar o nosso primeiro classificador de imagens:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ML5JS project</title>
    <script src="https://unpkg.com/ml5@0.3.1/dist/ml5.min.js"></script>
</head>
<body>
    <img src="./assets/dublin.jpg" id="foto" />
    <script>
        // Passo 1: Criar uma classificador de imagems com mobileNet
        const classifier = ml5.imageClassifier('MobileNet', onModelReady);

        function onModelReady() {
            // Passo 2: Selecionar uma image
            const img = document.querySelector("#foto")

            // Passo 3: Fazer a predicção
            let prediction = classifier.predict(img, gotResults);
        }

        // Passo 4: Fazer algo quando tivemos o resultado!
        function gotResults(err, results) {
            console.log(results);
            // e aqui acontece toda a mágica
        }
    </script>
</body>
</html>

No código acima temos exemplo fazemos a classificação da imagem que escolhemos acima, primeiramente fazemos o carregamento da lib em uma CDN, após adicionamos a imagem com uma tag img em nosso markup, temos uma tag script com o código que irá analisar nossa imagem.

Dentro da tag script o primeiro comando faz a chamada de uma função imageClassifier dentro da biblioteca ML5 passando dois parâmetros:

  • modelo – nesse caso vamos utilizar o mobileNet esse modelo pre-treinado está na nuvem ele possui mais de 14 mil imagens em seu dataset
  • função de callback – o segudo parâmetro a função de callback que iremos chamar quando nosso classificador de imagens estiver pronto.

Quando nosso modelo estiver pronto vamos chamar a função onModelReady dentro dela selecionamos a imagem que queremos analisar e com o nosso classificador de imagens(classifier) requisitamos a previsão da imagem. novamente passando dois parâmetros a imagens que iremos analisar e a função de callback.

Quando executamos o nosso código em um browser o resultado será o seguinte:

screenshot to console do browser

Um resultado meio estranho se traduzirmos o resultado temos label como “mastro” ou “poste” com confidência de 0.76 ou seja 76%. Como podemos ver temos uma farbrica no fundo com duas chamines.

A escolha da imagem foi proposital para a gente entender como funciona a analise de images, como falamos anteriormente MobileNet é um modelo pré-treinado mas ele tem uma base de 14 mil imagens o problema nessa caso será que o dentro dessas 14 mil imagens não treinamos o nosso modelo para identificar fábricas.

Se fizermos um segundo teste com essa imagem:

Segunda tentativa

O resultado será o seguinte:

Agora temos o resultado classificado como “beer glass”(copo de cerveja) com uma confidência de 94%, então como podemos ver não existe uma mágica por trás do Machine Learning.

Com isso entendemos que precisão é baseada na quantidade de informação em que meu dataset detém. Por isso empresas que detem uma grande base de informação conseguem aplicar o tecnologia a várias soluções.

Caso queira conferir mais tutoriais sobre JavaScript aqui confira o link: https://blog.fellyph.com.br/category/javascript/

Para esse post vamos ficando por aqui a idéia que em breve estarei postando um vídeo em meu canal. Se ainda não segue o canal aqui está o link: https://www.youtube.com/user/fdangellys