diff --git a/.gitignore b/.gitignore
index ae47137..e36c928 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.clj-kondo/
.lsp/
-.vscode/
\ No newline at end of file
+.vscode/
+*py_cache__/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fe355d4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,31 @@
+# Changelog
+
+Todas as mudanças notáveis neste projeto serão documentadas neste arquivo.
+
+O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
+e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
+
+## [Não Lançado]
+
+### Adicionado
+- Templates de Issues (Bug Report e Feature Request)
+- Template de Pull Request
+- Guia de Contribuição (CONTRIBUTING.md)
+- Workflow de CI/CD com GitHub Actions
+- Testes unitários para gerador de senhas Python
+
+### Melhorado
+- Implementação completa do gerador de senhas em Python
+- Adicionada validação de força de senha
+- Adicionada função para gerar múltiplas senhas
+
+## [1.0.0] - 2026-01-04
+
+### Adicionado
+- Coleção inicial de projetos (iniciante, intermediário, avançado)
+- README principal com descrição dos projetos
+- Implementação JavaScript do gerador de senhas
+- Implementação Python do gerador de senhas
+
+[Não Lançado]: https://github.com/Ryanditko/coding-projects-library/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/Ryanditko/coding-projects-library/releases/tag/v1.0.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..1a11ad9
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,61 @@
+# Guia de Contribuição
+
+Obrigado por considerar contribuir para a Projects Library! 🎉
+
+## Como Contribuir
+
+### 1. Reportar Issues
+- Descreva o problema claramente
+- Inclua passos para reproduzir
+- Mencione o ambiente (OS, versão do Python/Node, etc.)
+
+### 2. Sugerir Melhorias
+- Abra uma issue descrevendo a melhoria
+- Explique o benefício da mudança
+- Discuta possíveis implementações
+
+### 3. Pull Requests
+- Faça fork do repositório
+- Crie uma branch para sua feature (`git checkout -b feature/MinhaFeature`)
+- Commit suas mudanças (`git commit -m 'Adiciona MinhaFeature'`)
+- Push para a branch (`git push origin feature/MinhaFeature`)
+- Abra um Pull Request
+
+### 4. Code Review
+- Revise PRs abertos
+- Forneça feedback construtivo
+- Sugira melhorias no código
+- Teste as mudanças propostas
+
+## Padrões de Código
+
+### Python
+- Siga PEP 8
+- Use docstrings
+- Adicione testes quando possível
+
+### JavaScript
+- Use ESLint
+- Comente código complexo
+- Mantenha funções pequenas e focadas
+
+## Estrutura dos Projetos
+
+Cada projeto deve ter:
+- README.md com descrição e instruções
+- Código bem documentado
+- Exemplos de uso
+
+## Processo de Review
+
+1. Todo PR será revisado por pelo menos um maintainer
+2. Feedback será fornecido em até 48 horas
+3. Mudanças solicitadas devem ser implementadas
+4. Após aprovação, o PR será merged
+
+## Dúvidas?
+
+Abra uma issue com a tag `question` ou entre em contato!
+
+---
+**Obrigado pela sua contribuição!**
diff --git a/iniciante/calculadora-simples/script.clj b/iniciante/calculadora-simples/script.clj
new file mode 100644
index 0000000..73953f5
--- /dev/null
+++ b/iniciante/calculadora-simples/script.clj
@@ -0,0 +1,37 @@
+(ns iniciante.calculadora-simples.script
+ (:require [clojure.string :as str]))
+
+(defn tokenize [expr]
+ (->> expr
+ (str/replace " " "")
+ (re-seq #"\d+(\.\d+)?|[+\-*/]")
+ (map #(if (re-matches #"\d+(\.\d+)?" %)
+ (Double/parseDouble %)
+ (first %)))))
+
+(defn resolve-high-precedence [tokens]
+ (loop [result []
+ [t & more] tokens]
+ (if (nil? t)
+ result
+ (if (#{\* \/} t)
+ (let [prev (peek result)
+ next (first more)
+ calc (if (= t \*)
+ (* prev next)
+ (/ prev next))]
+ (recur (conj (pop result) calc) (rest more)))
+ (recur (conj result t) more)))))
+
+(defn resolve-low-precedence [tokens]
+ (loop [acc (first tokens)
+ [op n & more] (rest tokens)]
+ (if (nil? op)
+ acc
+ (recur (case op
+ \+ (+ acc n)
+ \- (- acc n))
+ more))))
+
+(defn calcular [expr]
+ (-> expr tokenize resolve-high-precedence resolve-low-precedence))
diff --git a/iniciante/calculadora-simples/script.js b/iniciante/calculadora-simples/script.js
new file mode 100644
index 0000000..cc185ff
--- /dev/null
+++ b/iniciante/calculadora-simples/script.js
@@ -0,0 +1,89 @@
+// --- Função para transformar a string em tokens (números e operadores) ---
+function tokenize(expr) {
+ const tokens = [];
+ let current = '';
+
+ for (let char of expr) {
+ if (/[0-9.]/.test(char)) { // número ou ponto decimal
+ current += char;
+ } else if (/[+\-*/]/.test(char)) { // operador
+ if (current) tokens.push(parseFloat(current));
+ tokens.push(char);
+ current = '';
+ }
+ }
+ if (current) tokens.push(parseFloat(current));
+ return tokens;
+}
+
+// --- Resolve operações de alta precedência: * e / ---
+function resolveHighPrecedence(tokens) {
+ const result = [];
+ let i = 0;
+
+ while (i < tokens.length) {
+ const token = tokens[i];
+
+ if (token === '*' || token === '/') {
+ const prev = result.pop();
+ const next = tokens[i + 1];
+ const calc = token === '*'
+ ? prev * next
+ : prev / next;
+
+ result.push(calc);
+ i += 2; // pulamos o próximo porque já calculamos
+ } else {
+ result.push(token);
+ i++;
+ }
+ }
+
+ return result;
+}
+
+// --- Resolve baixa precedência: + e - ---
+function resolveLowPrecedence(tokens) {
+ let result = tokens[0];
+
+ for (let i = 1; i < tokens.length; i += 2) {
+ const operator = tokens[i];
+ const next = tokens[i + 1];
+
+ if (operator === '+') result += next;
+ if (operator === '-') result -= next;
+ }
+
+ return result;
+}
+
+// --- Função principal que junta tudo ---
+function calcular(expressao) {
+ const tokens = tokenize(expressao);
+ const etapa1 = resolveHighPrecedence(tokens);
+ const resultado = resolveLowPrecedence(etapa1);
+ return resultado;
+}
+
+// --- CLI ---
+const readline = require("readline").createInterface({
+ input: process.stdin,
+ output: process.stdout
+});
+
+console.log("Calculadora Simples | digite expressões como: 12+7*3-4/2");
+console.log("Para sair, pressione CTRL + C\n");
+
+function loop() {
+ readline.question("> ", (input) => {
+ try {
+ const resultado = calcular(input);
+ console.log("= " + resultado + "\n");
+ } catch (e) {
+ console.log("Erro na expressão\n");
+ }
+ loop(); // mantém a calculadora ativa
+ });
+}
+
+loop();
diff --git a/iniciante/calculadora-simples/script.py b/iniciante/calculadora-simples/script.py
new file mode 100644
index 0000000..4c4dd01
--- /dev/null
+++ b/iniciante/calculadora-simples/script.py
@@ -0,0 +1,88 @@
+import re
+
+# --- Tokenização: transforma expressão em números e operadores ---
+def tokenize(expr):
+ tokens = []
+ current = ''
+
+ for char in expr.replace(" ", ""): # remove espaços
+ if char.isdigit() or char == '.':
+ current += char
+ elif char in '+-*/':
+ if current:
+ tokens.append(float(current))
+ tokens.append(char)
+ current = ''
+ else:
+ raise ValueError(f"Caractere inválido: {char}")
+
+ if current:
+ tokens.append(float(current))
+ return tokens
+
+
+# --- Resolve operações de alta precedência: * e / ---
+def resolve_high_precedence(tokens):
+ result = []
+ i = 0
+
+ while i < len(tokens):
+ token = tokens[i]
+
+ if token in ('*', '/'):
+ prev = result.pop()
+ next_value = tokens[i + 1]
+
+ calc = prev * next_value if token == '*' else prev / next_value
+ result.append(calc)
+ i += 2
+ else:
+ result.append(token)
+ i += 1
+
+ return result
+
+
+# --- Resolve baixa precedência: + e - ---
+def resolve_low_precedence(tokens):
+ result = tokens[0]
+
+ for i in range(1, len(tokens), 2):
+ operator = tokens[i]
+ next_value = tokens[i + 1]
+
+ if operator == '+':
+ result += next_value
+ elif operator == '-':
+ result -= next_value
+
+ return result
+
+
+# --- Função principal ---
+def calcular(expressao):
+ tokens = tokenize(expressao)
+ etapa1 = resolve_high_precedence(tokens)
+ resultado = resolve_low_precedence(etapa1)
+ return resultado
+
+
+# --- CLI / Loop principal ---
+if __name__ == "__main__":
+ print("Calculadora CLI Simples | digite expressões como: 12+7*3-4/2")
+ print("Para sair use CTRL + C\n")
+
+ while True:
+ try:
+ entrada = input("> ")
+ if entrada.strip() == "":
+ continue
+
+ resultado = calcular(entrada)
+ print(f"= {resultado}\n")
+
+ except ZeroDivisionError:
+ print("Erro: divisão por zero não é permitida.\n")
+
+ except Exception as e:
+ print(f"Erro: {e}\n")
diff --git a/iniciante/contador-de-palavras/index.html b/iniciante/contador-de-palavras/index.html
new file mode 100644
index 0000000..1618b9f
--- /dev/null
+++ b/iniciante/contador-de-palavras/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+ ✍️ Contador de Palavras
+
+
+
+
+
+
✍️ Contador de Palavras
+
Digite ou cole seu texto abaixo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
⏱️
+
0 min
+
Tempo de Leitura
+
+
+
+
+
📊 Detalhes Avançados
+
+
+ Frases:
+ 0
+
+
+ Vogais:
+ 0
+
+
+ Consoantes:
+ 0
+
+
+ Palavra mais longa:
+ -
+
+
+
+
+
+
🔝 Palavras Mais Frequentes
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iniciante/contador-de-palavras/script.clj b/iniciante/contador-de-palavras/script.clj
new file mode 100644
index 0000000..1155a25
--- /dev/null
+++ b/iniciante/contador-de-palavras/script.clj
@@ -0,0 +1,363 @@
+(ns script
+ (:require [clojure.string :as str])
+ (:import [javax.swing JFrame JPanel JLabel JButton JTextArea JScrollPane
+ SwingUtilities BoxLayout JSplitPane]
+ [java.awt BorderLayout GridLayout FlowLayout Font Color Dimension]
+ [java.awt.event KeyAdapter]
+ [javax.swing.border EmptyBorder TitledBorder]))
+
+;; Estado global
+(def app-state
+ (atom {:text ""
+ :stats {:words 0
+ :chars 0
+ :chars-no-space 0
+ :lines 0
+ :paragraphs 0
+ :sentences 0
+ :vowels 0
+ :consonants 0
+ :longest-word ""
+ :reading-time "0 min"
+ :word-frequency []}}))
+
+;; Componentes UI
+(def ui-components (atom {}))
+
+;; Funções de análise
+
+(defn count-words
+ "Conta palavras no texto"
+ [text]
+ (if (str/blank? text)
+ 0
+ (count (re-seq #"\b\w+\b" text))))
+
+(defn count-chars-no-space
+ "Conta caracteres sem espaços"
+ [text]
+ (count (str/replace text #"\s" "")))
+
+(defn count-lines
+ "Conta linhas"
+ [text]
+ (if (empty? text)
+ 0
+ (count (str/split-lines text))))
+
+(defn count-paragraphs
+ "Conta parágrafos"
+ [text]
+ (if (str/blank? text)
+ 0
+ (count (filter #(not (str/blank? %))
+ (str/split text #"\n\s*\n")))))
+
+(defn count-sentences
+ "Conta frases"
+ [text]
+ (if (str/blank? text)
+ 0
+ (count (filter #(not (str/blank? %))
+ (str/split text #"[.!?]+")))))
+
+(defn count-vowels
+ "Conta vogais"
+ [text]
+ (count (re-seq #"[aeiouáéíóúâêîôûãõàèìòù]" (str/lower-case text))))
+
+(defn count-consonants
+ "Conta consoantes"
+ [text]
+ (count (re-seq #"[bcdfghjklmnpqrstvwxyzçñ]" (str/lower-case text))))
+
+(defn find-longest-word
+ "Encontra palavra mais longa"
+ [text]
+ (if (str/blank? text)
+ ""
+ (let [words (re-seq #"\b\w+\b" text)]
+ (if (empty? words)
+ ""
+ (apply max-key count words)))))
+
+(defn calculate-reading-time
+ "Calcula tempo de leitura (200 palavras/min)"
+ [word-count]
+ (if (zero? word-count)
+ "0 min"
+ (let [minutes (max 1 (quot word-count 200))]
+ (str minutes " min"))))
+
+(defn word-frequency
+ "Calcula frequência de palavras"
+ [text]
+ (if (str/blank? text)
+ []
+ (let [words (->> (str/lower-case text)
+ (re-seq #"\b\w+\b")
+ (filter #(> (count %) 2)))]
+ (->> words
+ frequencies
+ (sort-by val >)
+ (take 10)))))
+
+(defn analyze-text
+ "Analisa o texto completo"
+ [text]
+ (let [words (count-words text)
+ chars (count text)
+ chars-no-space (count-chars-no-space text)
+ lines (count-lines text)
+ paragraphs (count-paragraphs text)
+ sentences (count-sentences text)
+ vowels (count-vowels text)
+ consonants (count-consonants text)
+ longest (find-longest-word text)
+ reading-time (calculate-reading-time words)
+ freq (word-frequency text)]
+ {:words words
+ :chars chars
+ :chars-no-space chars-no-space
+ :lines lines
+ :paragraphs paragraphs
+ :sentences sentences
+ :vowels vowels
+ :consonants consonants
+ :longest-word longest
+ :reading-time reading-time
+ :word-frequency freq}))
+
+(defn update-display
+ "Atualiza todos os componentes da UI"
+ []
+ (SwingUtilities/invokeLater
+ (fn []
+ (let [stats (:stats @app-state)
+ {:keys [word-count char-count char-no-space-count
+ line-count paragraph-count reading-time-label
+ sentence-count vowel-count consonant-count
+ longest-word-label freq-area]} @ui-components]
+
+ (.setText word-count (str (:words stats)))
+ (.setText char-count (str (:chars stats)))
+ (.setText char-no-space-count (str (:chars-no-space stats)))
+ (.setText line-count (str (:lines stats)))
+ (.setText paragraph-count (str (:paragraphs stats)))
+ (.setText reading-time-label (:reading-time stats))
+ (.setText sentence-count (str (:sentences stats)))
+ (.setText vowel-count (str (:vowels stats)))
+ (.setText consonant-count (str (:consonants stats)))
+ (.setText longest-word-label (or (:longest-word stats) "-"))
+
+ ;; Atualizar frequência
+ (let [freq-text (if (empty? (:word-frequency stats))
+ "Nenhuma palavra ainda..."
+ (str/join "\n"
+ (map (fn [[word count]]
+ (format "%-20s %dx" word count))
+ (:word-frequency stats))))]
+ (.setText freq-area freq-text))))))
+
+(defn on-text-change
+ "Callback quando o texto muda"
+ [text-area]
+ (let [text (.getText text-area)
+ stats (analyze-text text)]
+ (swap! app-state assoc :text text :stats stats)
+ (update-display)))
+
+(defn create-stat-card
+ "Cria um card de estatística"
+ [icon value-atom label]
+ (let [panel (doto (JPanel.)
+ (.setLayout (BoxLayout. (JPanel.) BoxLayout/Y_AXIS))
+ (.setBackground (Color. 102 126 234))
+ (.setBorder (EmptyBorder. 15 15 15 15))
+ (.setPreferredSize (Dimension. 150 120)))
+ icon-label (doto (JLabel. icon)
+ (.setFont (Font. "Arial" Font/PLAIN 28))
+ (.setForeground Color/WHITE)
+ (.setAlignmentX JLabel/CENTER_ALIGNMENT))
+ value-label (doto (JLabel. "0")
+ (.setFont (Font. "Arial" Font/BOLD 24))
+ (.setForeground Color/WHITE)
+ (.setAlignmentX JLabel/CENTER_ALIGNMENT))
+ label-label (doto (JLabel. label)
+ (.setFont (Font. "Arial" Font/PLAIN 11))
+ (.setForeground Color/WHITE)
+ (.setAlignmentX JLabel/CENTER_ALIGNMENT))]
+
+ (.add panel icon-label)
+ (.add panel (javax.swing.Box/createVerticalStrut 5))
+ (.add panel value-label)
+ (.add panel (javax.swing.Box/createVerticalStrut 5))
+ (.add panel label-label)
+
+ (swap! ui-components assoc value-atom value-label)
+ panel))
+
+(defn create-text-area
+ "Cria área de texto"
+ []
+ (let [text-area (doto (JTextArea.)
+ (.setFont (Font. "Arial" Font/PLAIN 13))
+ (.setLineWrap true)
+ (.setWrapStyleWord true)
+ (.setBorder (EmptyBorder. 10 10 10 10)))
+ scroll-pane (JScrollPane. text-area)]
+
+ (.addKeyListener text-area
+ (proxy [KeyAdapter] []
+ (keyReleased [_]
+ (on-text-change text-area))))
+
+ (swap! ui-components assoc :text-area text-area)
+ scroll-pane))
+
+(defn create-stats-panel
+ "Cria painel de estatísticas"
+ []
+ (let [panel (JPanel. (GridLayout. 2 3 10 10))]
+ (.setBackground panel (Color. 240 240 240))
+ (.setBorder panel (EmptyBorder. 10 10 10 10))
+
+ (.add panel (create-stat-card "📝" :word-count "Palavras"))
+ (.add panel (create-stat-card "🔤" :char-count "Caracteres"))
+ (.add panel (create-stat-card "📄" :char-no-space-count "Sem Espaços"))
+ (.add panel (create-stat-card "📋" :line-count "Linhas"))
+ (.add panel (create-stat-card "📖" :paragraph-count "Parágrafos"))
+ (.add panel (create-stat-card "⏱️" :reading-time-label "Tempo Leitura"))
+
+ panel))
+
+(defn create-details-panel
+ "Cria painel de detalhes"
+ []
+ (let [panel (JPanel. (GridLayout. 2 4 10 10))]
+ (.setBackground panel Color/WHITE)
+ (.setBorder panel (TitledBorder. "📊 Detalhes Avançados"))
+
+ (doseq [[label key] [["Frases:" :sentence-count]
+ ["Vogais:" :vowel-count]
+ ["Consoantes:" :consonant-count]
+ ["Palavra mais longa:" :longest-word-label]]]
+ (.add panel (doto (JLabel. label)
+ (.setFont (Font. "Arial" Font/PLAIN 11))))
+ (let [value-label (doto (JLabel. "0")
+ (.setFont (Font. "Arial" Font/BOLD 11))
+ (.setForeground (Color. 102 126 234)))]
+ (.add panel value-label)
+ (swap! ui-components assoc key value-label)))
+
+ panel))
+
+(defn create-frequency-panel
+ "Cria painel de frequência"
+ []
+ (let [panel (JPanel. (BorderLayout.))]
+ (.setBackground panel Color/WHITE)
+ (.setBorder panel (TitledBorder. "🔝 Palavras Mais Frequentes"))
+
+ (let [freq-area (doto (JTextArea. "Nenhuma palavra ainda...")
+ (.setFont (Font. "Courier New" Font/PLAIN 11))
+ (.setEditable false)
+ (.setBackground Color/WHITE))
+ scroll-pane (JScrollPane. freq-area)]
+ (swap! ui-components assoc :freq-area freq-area)
+ (.add panel scroll-pane BorderLayout/CENTER))
+
+ panel))
+
+(defn create-button-panel
+ "Cria painel de botões"
+ [text-area]
+ (let [panel (JPanel. (FlowLayout. FlowLayout/CENTER 10 10))
+ clear-btn (doto (JButton. "🗑️ Limpar Texto")
+ (.setFont (Font. "Arial" Font/BOLD 12))
+ (.setBackground (Color. 244 67 54))
+ (.setForeground Color/WHITE)
+ (.setFocusPainted false)
+ (.addActionListener
+ (reify java.awt.event.ActionListener
+ (actionPerformed [_ _]
+ (.setText text-area "")
+ (on-text-change text-area)))))
+ copy-btn (doto (JButton. "📋 Copiar Estatísticas")
+ (.setFont (Font. "Arial" Font/BOLD 12))
+ (.setBackground (Color. 76 175 80))
+ (.setForeground Color/WHITE)
+ (.setFocusPainted false)
+ (.addActionListener
+ (reify java.awt.event.ActionListener
+ (actionPerformed [_ _]
+ (let [stats (:stats @app-state)
+ text (format "📊 ESTATÍSTICAS\n\nPalavras: %d\nCaracteres: %d\nLinhas: %d"
+ (:words stats)
+ (:chars stats)
+ (:lines stats))
+ clipboard (.getSystemClipboard (java.awt.Toolkit/getDefaultToolkit))
+ selection (java.awt.datatransfer.StringSelection. text)]
+ (.setContents clipboard selection nil)
+ (javax.swing.JOptionPane/showMessageDialog
+ nil "Estatísticas copiadas!" "✅ Sucesso"
+ javax.swing.JOptionPane/INFORMATION_MESSAGE))))))]
+
+ (.setBackground panel (Color. 240 240 240))
+ (.add panel clear-btn)
+ (.add panel copy-btn)
+ panel))
+
+(defn create-frame
+ "Cria a janela principal"
+ []
+ (let [frame (JFrame. "✍️ Contador de Palavras")
+ text-scroll (create-text-area)
+ text-area (:text-area @ui-components)
+
+ top-panel (doto (JPanel.)
+ (.setLayout (BoxLayout. (JPanel.) BoxLayout/Y_AXIS))
+ (.setBackground (Color. 240 240 240)))
+
+ stats-panel (create-stats-panel)
+ details-panel (create-details-panel)
+ freq-panel (create-frequency-panel)
+ button-panel (create-button-panel text-area)
+
+ main-panel (JPanel. (BorderLayout. 10 10))]
+
+ (.setDefaultCloseOperation frame JFrame/EXIT_ON_CLOSE)
+ (.setSize frame 950 850)
+
+ ;; Montar painel superior
+ (.add top-panel stats-panel)
+ (.add top-panel (javax.swing.Box/createVerticalStrut 10))
+ (.add top-panel details-panel)
+ (.add top-panel (javax.swing.Box/createVerticalStrut 10))
+ (.add top-panel freq-panel)
+ (.add top-panel (javax.swing.Box/createVerticalStrut 10))
+ (.add top-panel button-panel)
+
+ ;; Usar split pane
+ (let [split-pane (doto (JSplitPane. JSplitPane/VERTICAL_SPLIT text-scroll top-panel)
+ (.setResizeWeight 0.3)
+ (.setDividerLocation 200))]
+ (.add main-panel split-pane BorderLayout/CENTER))
+
+ (.setBackground main-panel (Color. 240 240 240))
+ (.setBorder main-panel (EmptyBorder. 20 20 20 20))
+
+ (.add frame main-panel)
+ (.setLocationRelativeTo frame nil)
+ frame))
+
+(defn -main
+ "Função principal"
+ [& _args]
+ (SwingUtilities/invokeLater
+ (fn []
+ (let [frame (create-frame)]
+ (.setVisible frame true)
+ (println "✍️ Contador de Palavras iniciado!")))))
+
+;; Para executar: (script/-main)
diff --git a/iniciante/contador-de-palavras/script.js b/iniciante/contador-de-palavras/script.js
new file mode 100644
index 0000000..44398b5
--- /dev/null
+++ b/iniciante/contador-de-palavras/script.js
@@ -0,0 +1,226 @@
+// Elementos DOM
+const textArea = document.getElementById('textArea');
+const wordCount = document.getElementById('wordCount');
+const charCount = document.getElementById('charCount');
+const charNoSpaces = document.getElementById('charNoSpaces');
+const lineCount = document.getElementById('lineCount');
+const paragraphCount = document.getElementById('paragraphCount');
+const readingTime = document.getElementById('readingTime');
+const sentenceCount = document.getElementById('sentenceCount');
+const vowelCount = document.getElementById('vowelCount');
+const consonantCount = document.getElementById('consonantCount');
+const longestWord = document.getElementById('longestWord');
+const frequencyList = document.getElementById('frequencyList');
+const clearBtn = document.getElementById('clearBtn');
+const copyStatsBtn = document.getElementById('copyStatsBtn');
+
+// Função principal de análise
+function analyzeText() {
+ const text = textArea.value;
+
+ // Contagem básica
+ const words = countWords(text);
+ const chars = text.length;
+ const charsNoSpace = text.replace(/\s/g, '').length;
+ const lines = countLines(text);
+ const paragraphs = countParagraphs(text);
+ const sentences = countSentences(text);
+ const readTime = calculateReadingTime(words);
+
+ // Contagem avançada
+ const vowels = countVowels(text);
+ const consonants = countConsonants(text);
+ const longest = findLongestWord(text);
+
+ // Atualizar display
+ wordCount.textContent = words;
+ charCount.textContent = chars;
+ charNoSpaces.textContent = charsNoSpace;
+ lineCount.textContent = lines;
+ paragraphCount.textContent = paragraphs;
+ readingTime.textContent = readTime;
+ sentenceCount.textContent = sentences;
+ vowelCount.textContent = vowels;
+ consonantCount.textContent = consonants;
+ longestWord.textContent = longest || '-';
+
+ // Frequência de palavras
+ updateWordFrequency(text);
+}
+
+// Contar palavras
+function countWords(text) {
+ if (!text.trim()) return 0;
+ // Remove múltiplos espaços e quebras de linha, depois conta palavras
+ const words = text.trim().split(/\s+/);
+ return words.filter(word => word.length > 0).length;
+}
+
+// Contar linhas
+function countLines(text) {
+ if (!text) return 0;
+ return text.split('\n').length;
+}
+
+// Contar parágrafos
+function countParagraphs(text) {
+ if (!text.trim()) return 0;
+ // Parágrafos são separados por linhas vazias
+ const paragraphs = text.split(/\n\s*\n/);
+ return paragraphs.filter(p => p.trim().length > 0).length;
+}
+
+// Contar frases
+function countSentences(text) {
+ if (!text.trim()) return 0;
+ // Frases terminam com . ! ?
+ const sentences = text.split(/[.!?]+/);
+ return sentences.filter(s => s.trim().length > 0).length;
+}
+
+// Calcular tempo de leitura (assumindo 200 palavras/minuto)
+function calculateReadingTime(words) {
+ if (words === 0) return '0 min';
+ const minutes = Math.ceil(words / 200);
+ return minutes === 1 ? '1 min' : `${minutes} min`;
+}
+
+// Contar vogais
+function countVowels(text) {
+ const matches = text.match(/[aeiouáéíóúâêîôûãõàèìòù]/gi);
+ return matches ? matches.length : 0;
+}
+
+// Contar consoantes
+function countConsonants(text) {
+ const matches = text.match(/[bcdfghjklmnpqrstvwxyzçñ]/gi);
+ return matches ? matches.length : 0;
+}
+
+// Encontrar palavra mais longa
+function findLongestWord(text) {
+ if (!text.trim()) return '';
+ const words = text.split(/\s+/);
+ const cleanWords = words.map(w => w.replace(/[^\w]/g, ''));
+ return cleanWords.reduce((longest, current) =>
+ current.length > longest.length ? current : longest, '');
+}
+
+// Atualizar frequência de palavras
+function updateWordFrequency(text) {
+ if (!text.trim()) {
+ frequencyList.innerHTML = 'Nenhuma palavra ainda...
';
+ return;
+ }
+
+ // Contar frequência
+ const words = text.toLowerCase()
+ .replace(/[^\w\sáéíóúâêîôûãõàèìòù]/g, '')
+ .split(/\s+/)
+ .filter(word => word.length > 2); // Ignorar palavras muito curtas
+
+ const frequency = {};
+ words.forEach(word => {
+ frequency[word] = (frequency[word] || 0) + 1;
+ });
+
+ // Ordenar por frequência (top 10)
+ const sorted = Object.entries(frequency)
+ .sort((a, b) => b[1] - a[1])
+ .slice(0, 10);
+
+ // Renderizar
+ if (sorted.length === 0) {
+ frequencyList.innerHTML = 'Nenhuma palavra significativa...
';
+ return;
+ }
+
+ frequencyList.innerHTML = sorted.map(([word, count]) => `
+
+ ${word}
+ ${count}x
+
+ `).join('');
+}
+
+// Limpar texto
+function clearText() {
+ textArea.value = '';
+ analyzeText();
+ textArea.focus();
+}
+
+// Copiar estatísticas
+function copyStats() {
+ const text = textArea.value;
+ const stats = `
+📊 ESTATÍSTICAS DO TEXTO
+
+📝 Palavras: ${wordCount.textContent}
+🔤 Caracteres: ${charCount.textContent}
+📄 Sem Espaços: ${charNoSpaces.textContent}
+📋 Linhas: ${lineCount.textContent}
+📖 Parágrafos: ${paragraphCount.textContent}
+⏱️ Tempo de Leitura: ${readingTime.textContent}
+
+📊 DETALHES AVANÇADOS
+
+Frases: ${sentenceCount.textContent}
+Vogais: ${vowelCount.textContent}
+Consoantes: ${consonantCount.textContent}
+Palavra mais longa: ${longestWord.textContent}
+ `.trim();
+
+ navigator.clipboard.writeText(stats).then(() => {
+ // Feedback visual
+ const originalText = copyStatsBtn.textContent;
+ copyStatsBtn.textContent = '✅ Copiado!';
+ copyStatsBtn.style.background = '#4caf50';
+
+ setTimeout(() => {
+ copyStatsBtn.textContent = originalText;
+ copyStatsBtn.style.background = '';
+ }, 2000);
+ }).catch(err => {
+ alert('Erro ao copiar: ' + err);
+ });
+}
+
+// Event Listeners
+textArea.addEventListener('input', analyzeText);
+clearBtn.addEventListener('click', clearText);
+copyStatsBtn.addEventListener('click', copyStats);
+
+// Atalhos de teclado
+document.addEventListener('keydown', (e) => {
+ // Ctrl/Cmd + K = Limpar
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
+ e.preventDefault();
+ clearText();
+ }
+ // Ctrl/Cmd + Shift + C = Copiar stats
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'C') {
+ e.preventDefault();
+ copyStats();
+ }
+});
+
+// Auto-save no localStorage
+textArea.addEventListener('input', () => {
+ localStorage.setItem('savedText', textArea.value);
+});
+
+// Carregar texto salvo
+window.addEventListener('load', () => {
+ const savedText = localStorage.getItem('savedText');
+ if (savedText) {
+ textArea.value = savedText;
+ analyzeText();
+ }
+});
+
+// Inicializar
+analyzeText();
+
+console.log('✍️ Contador de Palavras carregado!');
+console.log('Atalhos: Ctrl+K = Limpar | Ctrl+Shift+C = Copiar Stats');
diff --git a/iniciante/contador-de-palavras/script.py b/iniciante/contador-de-palavras/script.py
new file mode 100644
index 0000000..82680ec
--- /dev/null
+++ b/iniciante/contador-de-palavras/script.py
@@ -0,0 +1,346 @@
+import tkinter as tk
+from tkinter import ttk, messagebox, scrolledtext
+from collections import Counter
+import re
+
+
+class WordCounter:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("✍️ Contador de Palavras")
+ self.root.geometry("900x800")
+ self.root.configure(bg='#f0f0f0')
+
+ self.setup_ui()
+
+ def setup_ui(self):
+ # Container principal
+ main_frame = tk.Frame(self.root, bg='#f0f0f0')
+ main_frame.pack(expand=True, fill='both', padx=20, pady=20)
+
+ # Título
+ title_label = tk.Label(
+ main_frame,
+ text="✍️ Contador de Palavras",
+ font=('Arial', 28, 'bold'),
+ bg='#f0f0f0',
+ fg='#333'
+ )
+ title_label.pack(pady=(0, 5))
+
+ subtitle = tk.Label(
+ main_frame,
+ text="Digite ou cole seu texto abaixo",
+ font=('Arial', 12),
+ bg='#f0f0f0',
+ fg='#666'
+ )
+ subtitle.pack(pady=(0, 20))
+
+ # Área de texto
+ text_frame = tk.Frame(main_frame, bg='#f0f0f0')
+ text_frame.pack(fill='both', expand=True, pady=(0, 20))
+
+ self.text_area = scrolledtext.ScrolledText(
+ text_frame,
+ wrap=tk.WORD,
+ font=('Arial', 11),
+ bg='white',
+ fg='#333',
+ relief='solid',
+ borderwidth=2,
+ padx=10,
+ pady=10
+ )
+ self.text_area.pack(fill='both', expand=True)
+ self.text_area.bind('', lambda e: self.analyze_text())
+
+ # Grid de estatísticas
+ stats_frame = tk.Frame(main_frame, bg='#f0f0f0')
+ stats_frame.pack(fill='x', pady=(0, 20))
+
+ # Criar cards de estatísticas
+ self.stat_cards = {}
+ stats = [
+ ('📝', 'words', 'Palavras'),
+ ('🔤', 'chars', 'Caracteres'),
+ ('📄', 'chars_no_space', 'Sem Espaços'),
+ ('📋', 'lines', 'Linhas'),
+ ('📖', 'paragraphs', 'Parágrafos'),
+ ('⏱️', 'reading_time', 'Tempo Leitura')
+ ]
+
+ for i, (icon, key, label) in enumerate(stats):
+ card = self.create_stat_card(stats_frame, icon, label)
+ card.grid(row=i//3, column=i%3, padx=5, pady=5, sticky='nsew')
+ self.stat_cards[key] = card['value_label']
+
+ # Configurar grid
+ for i in range(3):
+ stats_frame.columnconfigure(i, weight=1)
+
+ # Detalhes avançados
+ details_frame = tk.LabelFrame(
+ main_frame,
+ text="📊 Detalhes Avançados",
+ font=('Arial', 12, 'bold'),
+ bg='white',
+ fg='#333',
+ relief='raised',
+ bd=2
+ )
+ details_frame.pack(fill='x', pady=(0, 20))
+
+ details_inner = tk.Frame(details_frame, bg='white')
+ details_inner.pack(padx=15, pady=15, fill='x')
+
+ self.detail_labels = {}
+ details = [
+ ('Frases:', 'sentences'),
+ ('Vogais:', 'vowels'),
+ ('Consoantes:', 'consonants'),
+ ('Palavra mais longa:', 'longest')
+ ]
+
+ for i, (label, key) in enumerate(details):
+ tk.Label(
+ details_inner,
+ text=label,
+ font=('Arial', 10),
+ bg='white',
+ fg='#666'
+ ).grid(row=i//2, column=(i%2)*2, sticky='w', padx=10, pady=5)
+
+ value_label = tk.Label(
+ details_inner,
+ text='0',
+ font=('Arial', 10, 'bold'),
+ bg='white',
+ fg='#667eea'
+ )
+ value_label.grid(row=i//2, column=(i%2)*2+1, sticky='e', padx=10, pady=5)
+ self.detail_labels[key] = value_label
+
+ # Configurar grid de detalhes
+ for i in range(4):
+ details_inner.columnconfigure(i, weight=1)
+
+ # Frequência de palavras
+ freq_frame = tk.LabelFrame(
+ main_frame,
+ text="🔝 Palavras Mais Frequentes",
+ font=('Arial', 12, 'bold'),
+ bg='white',
+ fg='#333',
+ relief='raised',
+ bd=2
+ )
+ freq_frame.pack(fill='both', expand=True, pady=(0, 20))
+
+ # Listbox para frequência
+ self.freq_listbox = tk.Listbox(
+ freq_frame,
+ font=('Arial', 10),
+ bg='white',
+ fg='#333',
+ height=8,
+ relief='flat'
+ )
+ self.freq_listbox.pack(fill='both', expand=True, padx=15, pady=15)
+
+ # Botões
+ button_frame = tk.Frame(main_frame, bg='#f0f0f0')
+ button_frame.pack(fill='x')
+
+ clear_btn = tk.Button(
+ button_frame,
+ text="🗑️ Limpar Texto",
+ command=self.clear_text,
+ font=('Arial', 11, 'bold'),
+ bg='#f44336',
+ fg='white',
+ relief='raised',
+ cursor='hand2',
+ padx=20,
+ pady=10
+ )
+ clear_btn.pack(side='left', expand=True, fill='x', padx=(0, 5))
+
+ copy_btn = tk.Button(
+ button_frame,
+ text="📋 Copiar Estatísticas",
+ command=self.copy_stats,
+ font=('Arial', 11, 'bold'),
+ bg='#4caf50',
+ fg='white',
+ relief='raised',
+ cursor='hand2',
+ padx=20,
+ pady=10
+ )
+ copy_btn.pack(side='right', expand=True, fill='x', padx=(5, 0))
+
+ # Inicializar
+ self.analyze_text()
+
+ def create_stat_card(self, parent, icon, label):
+ """Cria um card de estatística"""
+ card_frame = tk.Frame(parent, bg='#667eea', relief='raised', bd=0)
+ card_frame.pack_propagate(False)
+
+ tk.Label(
+ card_frame,
+ text=icon,
+ font=('Arial', 24),
+ bg='#667eea',
+ fg='white'
+ ).pack(pady=(10, 5))
+
+ value_label = tk.Label(
+ card_frame,
+ text='0',
+ font=('Arial', 20, 'bold'),
+ bg='#667eea',
+ fg='white'
+ )
+ value_label.pack()
+
+ tk.Label(
+ card_frame,
+ text=label,
+ font=('Arial', 9),
+ bg='#667eea',
+ fg='white'
+ ).pack(pady=(5, 10))
+
+ return {'frame': card_frame, 'value_label': value_label}
+
+ def analyze_text(self):
+ """Analisa o texto e atualiza estatísticas"""
+ text = self.text_area.get('1.0', tk.END)
+
+ # Contagem básica
+ words = self.count_words(text)
+ chars = len(text) - 1 # -1 para remover o \n final do tkinter
+ chars_no_space = len(re.sub(r'\s', '', text))
+ lines = text.count('\n')
+ paragraphs = self.count_paragraphs(text)
+ reading_time = self.calculate_reading_time(words)
+
+ # Contagem avançada
+ sentences = self.count_sentences(text)
+ vowels = len(re.findall(r'[aeiouáéíóúâêîôûãõàèìòù]', text, re.IGNORECASE))
+ consonants = len(re.findall(r'[bcdfghjklmnpqrstvwxyzçñ]', text, re.IGNORECASE))
+ longest = self.find_longest_word(text)
+
+ # Atualizar displays
+ self.stat_cards['words'].config(text=str(words))
+ self.stat_cards['chars'].config(text=str(chars))
+ self.stat_cards['chars_no_space'].config(text=str(chars_no_space))
+ self.stat_cards['lines'].config(text=str(lines))
+ self.stat_cards['paragraphs'].config(text=str(paragraphs))
+ self.stat_cards['reading_time'].config(text=reading_time)
+
+ self.detail_labels['sentences'].config(text=str(sentences))
+ self.detail_labels['vowels'].config(text=str(vowels))
+ self.detail_labels['consonants'].config(text=str(consonants))
+ self.detail_labels['longest'].config(text=longest or '-')
+
+ # Atualizar frequência
+ self.update_word_frequency(text)
+
+ def count_words(self, text):
+ """Conta palavras no texto"""
+ if not text.strip():
+ return 0
+ words = re.findall(r'\b\w+\b', text)
+ return len(words)
+
+ def count_paragraphs(self, text):
+ """Conta parágrafos"""
+ if not text.strip():
+ return 0
+ paragraphs = re.split(r'\n\s*\n', text.strip())
+ return len([p for p in paragraphs if p.strip()])
+
+ def count_sentences(self, text):
+ """Conta frases"""
+ if not text.strip():
+ return 0
+ sentences = re.split(r'[.!?]+', text)
+ return len([s for s in sentences if s.strip()])
+
+ def calculate_reading_time(self, words):
+ """Calcula tempo de leitura (200 palavras/min)"""
+ if words == 0:
+ return '0 min'
+ minutes = max(1, words // 200)
+ return f'{minutes} min'
+
+ def find_longest_word(self, text):
+ """Encontra palavra mais longa"""
+ words = re.findall(r'\b\w+\b', text)
+ if not words:
+ return ''
+ return max(words, key=len)
+
+ def update_word_frequency(self, text):
+ """Atualiza lista de palavras mais frequentes"""
+ self.freq_listbox.delete(0, tk.END)
+
+ if not text.strip():
+ self.freq_listbox.insert(0, "Nenhuma palavra ainda...")
+ return
+
+ # Contar frequência
+ words = re.findall(r'\b\w+\b', text.lower())
+ words = [w for w in words if len(w) > 2] # Ignorar palavras curtas
+
+ if not words:
+ self.freq_listbox.insert(0, "Nenhuma palavra significativa...")
+ return
+
+ frequency = Counter(words)
+ top_10 = frequency.most_common(10)
+
+ for word, count in top_10:
+ self.freq_listbox.insert(tk.END, f"{word:<20} {count}x")
+
+ def clear_text(self):
+ """Limpa o texto"""
+ self.text_area.delete('1.0', tk.END)
+ self.analyze_text()
+
+ def copy_stats(self):
+ """Copia estatísticas para clipboard"""
+ stats = f"""
+📊 ESTATÍSTICAS DO TEXTO
+
+📝 Palavras: {self.stat_cards['words'].cget('text')}
+🔤 Caracteres: {self.stat_cards['chars'].cget('text')}
+📄 Sem Espaços: {self.stat_cards['chars_no_space'].cget('text')}
+📋 Linhas: {self.stat_cards['lines'].cget('text')}
+📖 Parágrafos: {self.stat_cards['paragraphs'].cget('text')}
+⏱️ Tempo de Leitura: {self.stat_cards['reading_time'].cget('text')}
+
+📊 DETALHES AVANÇADOS
+
+Frases: {self.detail_labels['sentences'].cget('text')}
+Vogais: {self.detail_labels['vowels'].cget('text')}
+Consoantes: {self.detail_labels['consonants'].cget('text')}
+Palavra mais longa: {self.detail_labels['longest'].cget('text')}
+ """.strip()
+
+ self.root.clipboard_clear()
+ self.root.clipboard_append(stats)
+ messagebox.showinfo("✅ Copiado!", "Estatísticas copiadas para a área de transferência!")
+
+
+def main():
+ root = tk.Tk()
+ app = WordCounter(root)
+ root.mainloop()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/iniciante/contador-de-palavras/style.css b/iniciante/contador-de-palavras/style.css
new file mode 100644
index 0000000..4bfe3ff
--- /dev/null
+++ b/iniciante/contador-de-palavras/style.css
@@ -0,0 +1,252 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 20px;
+}
+
+.container {
+ max-width: 1000px;
+ margin: 0 auto;
+ background: white;
+ border-radius: 20px;
+ padding: 40px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+}
+
+h1 {
+ text-align: center;
+ color: #333;
+ margin-bottom: 10px;
+ font-size: 2.5rem;
+}
+
+.subtitle {
+ text-align: center;
+ color: #666;
+ margin-bottom: 30px;
+ font-size: 1.1rem;
+}
+
+.editor-section {
+ margin-bottom: 30px;
+}
+
+#textArea {
+ width: 100%;
+ min-height: 250px;
+ padding: 20px;
+ border: 2px solid #e0e0e0;
+ border-radius: 12px;
+ font-size: 1rem;
+ font-family: 'Segoe UI', sans-serif;
+ resize: vertical;
+ transition: border-color 0.3s ease;
+ line-height: 1.6;
+}
+
+#textArea:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+#textArea::placeholder {
+ color: #999;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.stat-card {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 25px;
+ border-radius: 15px;
+ text-align: center;
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+
+.stat-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
+}
+
+.stat-icon {
+ font-size: 2rem;
+ margin-bottom: 10px;
+}
+
+.stat-value {
+ font-size: 2rem;
+ font-weight: bold;
+ margin-bottom: 5px;
+}
+
+.stat-label {
+ font-size: 0.9rem;
+ opacity: 0.9;
+}
+
+.details-section {
+ background: #f9f9f9;
+ padding: 25px;
+ border-radius: 12px;
+ margin-bottom: 30px;
+}
+
+.details-section h3 {
+ color: #333;
+ margin-bottom: 20px;
+ font-size: 1.3rem;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 15px;
+}
+
+.detail-item {
+ display: flex;
+ justify-content: space-between;
+ padding: 12px;
+ background: white;
+ border-radius: 8px;
+ border-left: 4px solid #667eea;
+}
+
+.detail-label {
+ color: #666;
+ font-weight: 500;
+}
+
+.detail-value {
+ color: #333;
+ font-weight: bold;
+}
+
+.word-frequency {
+ background: #f9f9f9;
+ padding: 25px;
+ border-radius: 12px;
+ margin-bottom: 30px;
+}
+
+.word-frequency h3 {
+ color: #333;
+ margin-bottom: 20px;
+ font-size: 1.3rem;
+}
+
+.frequency-list {
+ display: grid;
+ gap: 10px;
+}
+
+.frequency-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 15px;
+ background: white;
+ border-radius: 8px;
+ transition: transform 0.2s ease;
+}
+
+.frequency-item:hover {
+ transform: translateX(5px);
+}
+
+.frequency-word {
+ font-weight: 600;
+ color: #667eea;
+}
+
+.frequency-count {
+ background: #667eea;
+ color: white;
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ font-weight: bold;
+}
+
+.button-group {
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+}
+
+.btn {
+ padding: 15px 30px;
+ font-size: 1rem;
+ font-weight: bold;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn-clear {
+ background: #f44336;
+ color: white;
+}
+
+.btn-copy {
+ background: #4caf50;
+ color: white;
+}
+
+@media (max-width: 768px) {
+ .container {
+ padding: 20px;
+ }
+
+ h1 {
+ font-size: 2rem;
+ }
+
+ .stats-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .button-group {
+ flex-direction: column;
+ }
+
+ .btn {
+ width: 100%;
+ }
+}
+
+@media (max-width: 480px) {
+ .stats-grid {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/iniciante/gerador-de-senha/__pycache__/script.cpython-311.pyc b/iniciante/gerador-de-senha/__pycache__/script.cpython-311.pyc
new file mode 100644
index 0000000..434983a
Binary files /dev/null and b/iniciante/gerador-de-senha/__pycache__/script.cpython-311.pyc differ
diff --git a/iniciante/gerador-de-senha/script.clj b/iniciante/gerador-de-senha/script.clj
new file mode 100644
index 0000000..ef30bfb
--- /dev/null
+++ b/iniciante/gerador-de-senha/script.clj
@@ -0,0 +1,130 @@
+(ns script
+ (:require [clojure.string :as str]))
+
+;; Definição dos conjuntos de caracteres
+(def letras-maiusculas "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+(def letras-minusculas "abcdefghijklmnopqrstuvwxyz")
+(def numeros "0123456789")
+(def simbolos "!@#$%^&*()_+[]{}|;:,.<>?")
+
+(defn gerar-senha
+ "Gera uma senha aleatória com base nos parâmetros fornecidos.
+
+ Parâmetros:
+ tamanho - Comprimento da senha (número)
+ opcoes - Map com chaves booleanas:
+ :maiusculas? - Incluir letras maiúsculas (padrão: true)
+ :minusculas? - Incluir letras minúsculas (padrão: true)
+ :numeros? - Incluir números (padrão: true)
+ :simbolos? - Incluir símbolos especiais (padrão: true)
+
+ Retorna:
+ String com a senha gerada ou mensagem de erro"
+ ([tamanho]
+ (gerar-senha tamanho {}))
+ ([tamanho {:keys [maiusculas? minusculas? numeros? simbolos?]
+ :or {maiusculas? true
+ minusculas? true
+ numeros? true
+ simbolos? true}}]
+ (let [caracteres-disponiveis (str/join ""
+ (cond-> []
+ maiusculas? (conj letras-maiusculas)
+ minusculas? (conj letras-minusculas)
+ numeros? (conj numeros)
+ simbolos? (conj simbolos)))]
+ (if (empty? caracteres-disponiveis)
+ "Erro: Selecione pelo menos um tipo de caractere!"
+ (str/join (repeatedly tamanho
+ #(rand-nth caracteres-disponiveis)))))))
+
+(defn gerar-multiplas-senhas
+ "Gera múltiplas senhas de uma vez.
+
+ Parâmetros:
+ quantidade - Número de senhas a gerar
+ tamanho - Comprimento de cada senha (padrão: 12)
+ opcoes - Map de opções (mesmas da função gerar-senha)
+
+ Retorna:
+ Vetor de strings com as senhas geradas"
+ ([quantidade]
+ (gerar-multiplas-senhas quantidade 12 {}))
+ ([quantidade tamanho]
+ (gerar-multiplas-senhas quantidade tamanho {}))
+ ([quantidade tamanho opcoes]
+ (vec (repeatedly quantidade #(gerar-senha tamanho opcoes)))))
+
+(defn validar-forca-senha
+ "Valida a força de uma senha com base em critérios.
+
+ Parâmetros:
+ senha - String da senha a validar
+
+ Retorna:
+ String: 'Fraca', 'Média' ou 'Forte'"
+ [senha]
+ (let [tem-maiuscula? (some #(Character/isUpperCase %) senha)
+ tem-minuscula? (some #(Character/isLowerCase %) senha)
+ tem-numero? (some #(Character/isDigit %) senha)
+ tem-simbolo? (some #(str/includes? simbolos (str %)) senha)
+ tamanho-bom? (>= (count senha) 12)
+ forca (cond-> 0
+ tem-maiuscula? inc
+ tem-minuscula? inc
+ tem-numero? inc
+ tem-simbolo? inc
+ tamanho-bom? inc)]
+ (cond
+ (<= forca 2) "Fraca"
+ (<= forca 3) "Média"
+ :else "Forte")))
+
+(defn exibir-senha-formatada
+ "Exibe a senha com informações de força."
+ [senha]
+ (println (str "Senha gerada: " senha))
+ (println (str "Força: " (validar-forca-senha senha)))
+ (println (str "Tamanho: " (count senha) " caracteres\n")))
+
+;; Exemplos de uso
+(defn -main
+ "Função principal com exemplos de uso."
+ [& _args]
+ (println "=== Gerador de Senhas em Clojure ===\n")
+
+ ;; Exemplo 1: Senha padrão com 16 caracteres
+ (println "1. Senha com 16 caracteres (todos os tipos):")
+ (let [senha1 (gerar-senha 16)]
+ (exibir-senha-formatada senha1))
+
+ ;; Exemplo 2: Senha sem símbolos
+ (println "2. Senha de 12 caracteres sem símbolos:")
+ (let [senha2 (gerar-senha 12 {:simbolos? false})]
+ (exibir-senha-formatada senha2))
+
+ ;; Exemplo 3: Apenas letras e números
+ (println "3. Senha de 10 caracteres (apenas letras e números):")
+ (let [senha3 (gerar-senha 10 {:simbolos? false})]
+ (exibir-senha-formatada senha3))
+
+ ;; Exemplo 4: Apenas números (PIN)
+ (println "4. PIN de 6 dígitos:")
+ (let [pin (gerar-senha 6 {:maiusculas? false
+ :minusculas? false
+ :simbolos? false})]
+ (exibir-senha-formatada pin))
+
+ ;; Exemplo 5: Múltiplas senhas
+ (println "5. Gerando 3 senhas de 12 caracteres:")
+ (doseq [[idx senha] (map-indexed vector (gerar-multiplas-senhas 3 12))]
+ (println (str " " (inc idx) ". " senha " - Força: " (validar-forca-senha senha)))))
+
+;; Executar exemplos (descomente para testar)
+;; (-main)
+
+;; REPL Usage Examples:
+;; (gerar-senha 12)
+;; (gerar-senha 16 {:simbolos? false})
+;; (gerar-multiplas-senhas 5 10)
+;; (validar-forca-senha "Abc123!@#XYZ etc")
diff --git a/iniciante/gerador-de-senha/script.js b/iniciante/gerador-de-senha/script.js
new file mode 100644
index 0000000..1fbb79b
--- /dev/null
+++ b/iniciante/gerador-de-senha/script.js
@@ -0,0 +1,28 @@
+const letrasMaiusculas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+const letrasMinusculas = "abcdefghijklmnopqrstuvwxyz";
+const numeros = "0123456789";
+const simbolos = "!@#$%^&*()_+[]{}|;:,.<>?";
+
+function gerarSenha(tamanho, incluirMaiusculas = true, incluirMinusculas = true, incluirNumeros = true, incluirSimbolos = true) {
+ let caracteresDisponiveis = "";
+
+ if (incluirMaiusculas) caracteresDisponiveis += letrasMaiusculas;
+ if (incluirMinusculas) caracteresDisponiveis += letrasMinusculas;
+ if (incluirNumeros) caracteresDisponiveis += numeros;
+ if (incluirSimbolos) caracteresDisponiveis += simbolos;
+
+ if (caracteresDisponiveis === "") {
+ return "Erro: Selecione pelo menos um tipo de caractere!";
+ }
+
+ let senha = "";
+ for (let i = 0; i < tamanho; i++) {
+ senha += caracteresDisponiveis.charAt(Math.floor(Math.random() * caracteresDisponiveis.length));
+ }
+
+ return senha;
+}
+
+// Gerar e exibir senha com 12 caracteres
+const senhaGerada = gerarSenha(12);
+console.log("Senha gerada:", senhaGerada);
\ No newline at end of file
diff --git a/iniciante/gerador-de-senha/script.py b/iniciante/gerador-de-senha/script.py
new file mode 100644
index 0000000..b85b813
--- /dev/null
+++ b/iniciante/gerador-de-senha/script.py
@@ -0,0 +1,81 @@
+import random as rd
+import string
+
+def gerar_senha(tamanho, incluir_maiusculas=True, incluir_minusculas=True,
+ incluir_numeros=True, incluir_simbolos=True):
+ """
+ Gera uma senha aleatória com base nos parâmetros fornecidos.
+
+ Args:
+ tamanho (int): Comprimento da senha
+ incluir_maiusculas (bool): Incluir letras maiúsculas
+ incluir_minusculas (bool): Incluir letras minúsculas
+ incluir_numeros (bool): Incluir números
+ incluir_simbolos (bool): Incluir símbolos especiais
+
+ Returns:
+ str: Senha gerada
+ """
+ caracteres_disponiveis = ""
+
+ if incluir_maiusculas:
+ caracteres_disponiveis += string.ascii_uppercase
+ if incluir_minusculas:
+ caracteres_disponiveis += string.ascii_lowercase
+ if incluir_numeros:
+ caracteres_disponiveis += string.digits
+ if incluir_simbolos:
+ caracteres_disponiveis += "!@#$%^&*()_+[]{}|;:,.<>?"
+
+ if not caracteres_disponiveis:
+ return "Erro: Selecione pelo menos um tipo de caractere!"
+
+ senha = ''.join(rd.choice(caracteres_disponiveis) for _ in range(tamanho))
+ return senha
+
+
+def gerar_multiplas_senhas(quantidade, tamanho=12):
+ """Gera múltiplas senhas de uma vez."""
+ return [gerar_senha(tamanho) for _ in range(quantidade)]
+
+
+def validar_forca_senha(senha):
+ """Valida a força de uma senha."""
+ forca = 0
+
+ if any(c.isupper() for c in senha):
+ forca += 1
+ if any(c.islower() for c in senha):
+ forca += 1
+ if any(c.isdigit() for c in senha):
+ forca += 1
+ if any(c in "!@#$%^&*()_+[]{}|;:,.<>?" for c in senha):
+ forca += 1
+
+ if len(senha) >= 12:
+ forca += 1
+
+ if forca <= 2:
+ return "Fraca"
+ elif forca <= 3:
+ return "Média"
+ else:
+ return "Forte"
+
+
+if __name__ == "__main__":
+ # Exemplo de uso
+ print("=== Gerador de Senhas ===\n")
+
+ senha = gerar_senha(16)
+ print(f"Senha gerada (16 caracteres): {senha}")
+ print(f"Força da senha: {validar_forca_senha(senha)}\n")
+
+ senha_sem_simbolos = gerar_senha(12, incluir_simbolos=False)
+ print(f"Senha sem símbolos: {senha_sem_simbolos}")
+ print(f"Força da senha: {validar_forca_senha(senha_sem_simbolos)}\n")
+
+ senhas_multiplas = gerar_multiplas_senhas(3, 10)
+ print("3 senhas de 10 caracteres:")
+ for i, s in enumerate(senhas_multiplas, 1):
+ print(f" {i}. {s}")
\ No newline at end of file
diff --git a/iniciante/temporizador-pomodoro/index.html b/iniciante/temporizador-pomodoro/index.html
new file mode 100644
index 0000000..489dfa3
--- /dev/null
+++ b/iniciante/temporizador-pomodoro/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ ⏳ Temporizador Pomodoro
+
+
+
+
+
+
🍅 Temporizador Pomodoro
+
+
+
+
+
+
+
+
+
+
+
+ Ciclos Completos:
+ 0
+
+
+ Sessões de Foco:
+ 0
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iniciante/temporizador-pomodoro/script.clj b/iniciante/temporizador-pomodoro/script.clj
new file mode 100644
index 0000000..c2c9be6
--- /dev/null
+++ b/iniciante/temporizador-pomodoro/script.clj
@@ -0,0 +1,343 @@
+(ns script
+ (:import [javax.swing JFrame JPanel JLabel JButton JSpinner SpinnerNumberModel
+ SwingUtilities JProgressBar BoxLayout Timer]
+ [java.awt BorderLayout GridLayout FlowLayout Font Color Dimension]
+ [java.awt.event ActionListener]
+ [javax.swing.border EmptyBorder TitledBorder]))
+
+;; Estado global do Pomodoro
+(def app-state
+ (atom {:time-left (* 25 60) ; 25 minutos em segundos
+ :total-time (* 25 60)
+ :is-running false
+ :current-mode :focus ; :focus, :short-break, :long-break
+ :focus-sessions 0
+ :total-cycles 0
+ :config {:focus-time 25
+ :short-break-time 5
+ :long-break-time 15}
+ :timer nil}))
+
+;; Componentes da UI
+(def ui-components (atom {}))
+
+;; Funções auxiliares
+
+(defn format-time
+ "Formata segundos em MM:SS"
+ [seconds]
+ (let [mins (quot seconds 60)
+ secs (mod seconds 60)]
+ (format "%02d:%02d" mins secs)))
+
+(defn get-time-for-mode
+ "Retorna tempo em segundos para o modo"
+ [config mode]
+ (* 60 (case mode
+ :focus (:focus-time config)
+ :short-break (:short-break-time config)
+ :long-break (:long-break-time config))))
+
+(defn play-beep
+ "Toca um beep de notificação"
+ []
+ (try
+ (java.awt.Toolkit/getDefaultToolkit)
+ (.beep (java.awt.Toolkit/getDefaultToolkit))
+ (catch Exception e
+ (println "Erro ao tocar som:" (.getMessage e)))))
+
+(defn update-display
+ "Atualiza os componentes da UI"
+ []
+ (SwingUtilities/invokeLater
+ (fn []
+ (let [state @app-state
+ {:keys [time-left current-mode focus-sessions total-cycles]} state
+ {:keys [time-label mode-label progress-bar
+ focus-count-label cycles-count-label]} @ui-components
+ mode-labels {:focus "🎯 FOCO"
+ :short-break "☕ PAUSA CURTA"
+ :long-break "🌴 PAUSA LONGA"}
+ percentage (if (> (:total-time state) 0)
+ (int (* 100 (/ time-left (:total-time state))))
+ 100)]
+
+ (.setText time-label (format-time time-left))
+ (.setText mode-label (mode-labels current-mode))
+ (.setValue progress-bar percentage)
+ (.setText focus-count-label (str focus-sessions))
+ (.setText cycles-count-label (str total-cycles))))))
+
+(defn switch-mode
+ "Troca o modo do timer"
+ [mode]
+ (let [config (:config @app-state)
+ new-time (get-time-for-mode config mode)]
+ (swap! app-state assoc
+ :current-mode mode
+ :time-left new-time
+ :total-time new-time)
+ (update-display)))
+
+(defn complete-session
+ "Completa uma sessão"
+ []
+ (play-beep)
+ (let [state @app-state
+ mode (:current-mode state)]
+ (if (= mode :focus)
+ (do
+ (swap! app-state update :focus-sessions inc)
+ (let [sessions (:focus-sessions @app-state)]
+ (if (zero? (mod sessions 4))
+ (do
+ (swap! app-state update :total-cycles inc)
+ (switch-mode :long-break)
+ (javax.swing.JOptionPane/showMessageDialog
+ nil
+ "Você completou 4 sessões! Hora de uma pausa longa!"
+ "🌴 Pausa Longa"
+ javax.swing.JOptionPane/INFORMATION_MESSAGE))
+ (do
+ (switch-mode :short-break)
+ (javax.swing.JOptionPane/showMessageDialog
+ nil
+ "Sessão de foco completa! Hora de pausar!"
+ "☕ Pausa"
+ javax.swing.JOptionPane/INFORMATION_MESSAGE)))))
+ (do
+ (switch-mode :focus)
+ (javax.swing.JOptionPane/showMessageDialog
+ nil
+ "Pausa terminada! Hora de focar!"
+ "🎯 Foco"
+ javax.swing.JOptionPane/INFORMATION_MESSAGE)))))
+
+(defn tick-timer
+ "Decrementa o timer"
+ []
+ (when (:is-running @app-state)
+ (swap! app-state update :time-left dec)
+ (update-display)
+ (when (<= (:time-left @app-state) 0)
+ (swap! app-state assoc :is-running false)
+ (when-let [timer (:timer @app-state)]
+ (.stop timer))
+ (complete-session))))
+
+(defn start-timer
+ "Inicia o timer"
+ []
+ (when-not (:is-running @app-state)
+ (swap! app-state assoc :is-running true)
+ (let [timer (Timer. 1000 (reify ActionListener
+ (actionPerformed [_ _]
+ (tick-timer))))]
+ (swap! app-state assoc :timer timer)
+ (.start timer)
+ (let [{:keys [start-btn pause-btn]} @ui-components]
+ (.setEnabled start-btn false)
+ (.setEnabled pause-btn true)))))
+
+(defn pause-timer
+ "Pausa o timer"
+ []
+ (when (:is-running @app-state)
+ (swap! app-state assoc :is-running false)
+ (when-let [timer (:timer @app-state)]
+ (.stop timer))
+ (let [{:keys [start-btn pause-btn]} @ui-components]
+ (.setEnabled start-btn true)
+ (.setEnabled pause-btn false))))
+
+(defn reset-timer
+ "Reseta o timer"
+ []
+ (pause-timer)
+ (let [config (:config @app-state)
+ mode (:current-mode @app-state)
+ new-time (get-time-for-mode config mode)]
+ (swap! app-state assoc
+ :time-left new-time
+ :total-time new-time)
+ (update-display)))
+
+(defn save-config
+ "Salva configurações"
+ [focus short-break long-break]
+ (swap! app-state assoc-in [:config :focus-time] focus)
+ (swap! app-state assoc-in [:config :short-break-time] short-break)
+ (swap! app-state assoc-in [:config :long-break-time] long-break)
+ (when (= (:current-mode @app-state) :focus)
+ (reset-timer)))
+
+;; Criação da UI
+
+(defn create-button
+ "Cria um botão customizado"
+ [text bg-color action]
+ (doto (JButton. text)
+ (.setFont (Font. "Arial" Font/BOLD 14))
+ (.setBackground bg-color)
+ (.setForeground Color/WHITE)
+ (.setFocusPainted false)
+ (.setPreferredSize (Dimension. 120 50))
+ (.addActionListener (reify ActionListener
+ (actionPerformed [_ _]
+ (action))))))
+
+(defn create-timer-panel
+ "Cria o painel do timer"
+ []
+ (let [panel (JPanel.)
+ mode-label (doto (JLabel. "🎯 FOCO")
+ (.setFont (Font. "Arial" Font/BOLD 18))
+ (.setForeground (Color. 102 126 234))
+ (.setHorizontalAlignment JLabel/CENTER))
+ time-label (doto (JLabel. "25:00")
+ (.setFont (Font. "Courier New" Font/BOLD 60))
+ (.setHorizontalAlignment JLabel/CENTER))
+ progress-bar (doto (JProgressBar. 0 100)
+ (.setValue 100)
+ (.setPreferredSize (Dimension. 400 15)))]
+
+ (.setLayout panel (BoxLayout. panel BoxLayout/Y_AXIS))
+ (.setBackground panel Color/WHITE)
+ (.setBorder panel (EmptyBorder. 20 20 20 20))
+
+ (.add panel mode-label)
+ (.add panel (javax.swing.Box/createVerticalStrut 10))
+ (.add panel time-label)
+ (.add panel (javax.swing.Box/createVerticalStrut 20))
+ (.add panel progress-bar)
+
+ (swap! ui-components assoc
+ :mode-label mode-label
+ :time-label time-label
+ :progress-bar progress-bar)
+
+ panel))
+
+(defn create-control-panel
+ "Cria o painel de controles"
+ []
+ (let [panel (JPanel. (FlowLayout. FlowLayout/CENTER 10 10))
+ start-btn (create-button "▶ Iniciar" (Color. 76 175 80) start-timer)
+ pause-btn (create-button "⏸ Pausar" (Color. 255 152 0) pause-timer)
+ reset-btn (create-button "↻ Resetar" (Color. 244 67 54) reset-timer)]
+
+ (.setEnabled pause-btn false)
+ (.setBackground panel (Color. 240 240 240))
+
+ (.add panel start-btn)
+ (.add panel pause-btn)
+ (.add panel reset-btn)
+
+ (swap! ui-components assoc
+ :start-btn start-btn
+ :pause-btn pause-btn)
+
+ panel))
+
+(defn create-stats-panel
+ "Cria o painel de estatísticas"
+ []
+ (let [panel (JPanel. (GridLayout. 2 2 20 10))
+ cycles-label (JLabel. "0")
+ focus-label (JLabel. "0")]
+
+ (.setBorder panel (TitledBorder. "📊 Estatísticas"))
+ (.setBackground panel Color/WHITE)
+
+ (.setFont cycles-label (Font. "Arial" Font/BOLD 14))
+ (.setFont focus-label (Font. "Arial" Font/BOLD 14))
+ (.setForeground cycles-label (Color. 102 126 234))
+ (.setForeground focus-label (Color. 102 126 234))
+
+ (.add panel (JLabel. "Ciclos Completos:"))
+ (.add panel cycles-label)
+ (.add panel (JLabel. "Sessões de Foco:"))
+ (.add panel focus-label)
+
+ (swap! ui-components assoc
+ :cycles-count-label cycles-label
+ :focus-count-label focus-label)
+
+ panel))
+
+(defn create-settings-panel
+ "Cria o painel de configurações"
+ []
+ (let [panel (JPanel. (GridLayout. 3 2 10 10))
+ config (:config @app-state)
+ focus-spinner (JSpinner. (SpinnerNumberModel. (:focus-time config) 1 60 1))
+ short-spinner (JSpinner. (SpinnerNumberModel. (:short-break-time config) 1 30 1))
+ long-spinner (JSpinner. (SpinnerNumberModel. (:long-break-time config) 1 60 1))
+ update-config #(save-config
+ (.getValue focus-spinner)
+ (.getValue short-spinner)
+ (.getValue long-spinner))]
+
+ (.setBorder panel (TitledBorder. "⚙️ Configurações"))
+ (.setBackground panel Color/WHITE)
+
+ (.addChangeListener focus-spinner (reify javax.swing.event.ChangeListener
+ (stateChanged [_ _] (update-config))))
+ (.addChangeListener short-spinner (reify javax.swing.event.ChangeListener
+ (stateChanged [_ _] (update-config))))
+ (.addChangeListener long-spinner (reify javax.swing.event.ChangeListener
+ (stateChanged [_ _] (update-config))))
+
+ (.add panel (JLabel. "Tempo de Foco (min):"))
+ (.add panel focus-spinner)
+ (.add panel (JLabel. "Pausa Curta (min):"))
+ (.add panel short-spinner)
+ (.add panel (JLabel. "Pausa Longa (min):"))
+ (.add panel long-spinner)
+
+ panel))
+
+(defn create-frame
+ "Cria a janela principal"
+ []
+ (let [frame (JFrame. "🍅 Temporizador Pomodoro")
+ main-panel (JPanel. (BorderLayout. 10 10))]
+
+ (.setDefaultCloseOperation frame JFrame/EXIT_ON_CLOSE)
+ (.setSize frame 550 700)
+ (.setResizable frame false)
+
+ (.setBackground main-panel (Color. 240 240 240))
+ (.setBorder main-panel (EmptyBorder. 20 20 20 20))
+
+ ;; Adicionar componentes
+ (.add main-panel (create-timer-panel) BorderLayout/NORTH)
+ (.add main-panel (create-control-panel) BorderLayout/CENTER)
+
+ (let [bottom-panel (JPanel.)
+ _ (.setLayout bottom-panel (BoxLayout. bottom-panel BoxLayout/Y_AXIS))
+ _ (.setBackground bottom-panel (Color. 240 240 240))]
+ (.add bottom-panel (create-stats-panel))
+ (.add bottom-panel (javax.swing.Box/createVerticalStrut 10))
+ (.add bottom-panel (create-settings-panel))
+ (.add main-panel bottom-panel BorderLayout/SOUTH))
+
+ (.add frame main-panel)
+ (.setLocationRelativeTo frame nil)
+ frame))
+
+(defn -main
+ "Função principal"
+ [& _args]
+ (SwingUtilities/invokeLater
+ (fn []
+ (let [frame (create-frame)]
+ (.setVisible frame true)
+ (println "🍅 Temporizador Pomodoro iniciado!")
+ (println "Configurações:")
+ (println " Foco: 25 minutos")
+ (println " Pausa curta: 5 minutos")
+ (println " Pausa longa: 15 minutos")))))
+
+;; Para executar: (script/-main)
diff --git a/iniciante/temporizador-pomodoro/script.js b/iniciante/temporizador-pomodoro/script.js
new file mode 100644
index 0000000..05ba245
--- /dev/null
+++ b/iniciante/temporizador-pomodoro/script.js
@@ -0,0 +1,241 @@
+// Estado do Pomodoro
+let timerInterval = null;
+let timeLeft = 25 * 60; // 25 minutos em segundos
+let totalTime = 25 * 60;
+let isRunning = false;
+let currentMode = 'focus'; // 'focus', 'shortBreak', 'longBreak'
+let focusSessionsCompleted = 0;
+let totalCycles = 0;
+
+// Elementos DOM
+const timeDisplay = document.getElementById('timeDisplay');
+const modeLabel = document.getElementById('modeLabel');
+const progressBar = document.getElementById('progressBar');
+const startBtn = document.getElementById('startBtn');
+const pauseBtn = document.getElementById('pauseBtn');
+const resetBtn = document.getElementById('resetBtn');
+const cyclesCount = document.getElementById('cyclesCount');
+const focusCount = document.getElementById('focusCount');
+const focusTimeInput = document.getElementById('focusTime');
+const shortBreakTimeInput = document.getElementById('shortBreakTime');
+const longBreakTimeInput = document.getElementById('longBreakTime');
+
+// Configurações de tempo (em minutos)
+const config = {
+ focusTime: 25,
+ shortBreakTime: 5,
+ longBreakTime: 15
+};
+
+// Inicialização
+function init() {
+ updateDisplay();
+ updateProgress();
+ loadSettings();
+}
+
+// Formatar tempo (segundos para MM:SS)
+function formatTime(seconds) {
+ const mins = Math.floor(seconds / 60);
+ const secs = seconds % 60;
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
+}
+
+// Atualizar display
+function updateDisplay() {
+ timeDisplay.textContent = formatTime(timeLeft);
+
+ const modeLabels = {
+ focus: '🎯 Foco',
+ shortBreak: '☕ Pausa Curta',
+ longBreak: '🌴 Pausa Longa'
+ };
+ modeLabel.textContent = modeLabels[currentMode];
+}
+
+// Atualizar barra de progresso
+function updateProgress() {
+ const percentage = (timeLeft / totalTime) * 100;
+ progressBar.style.width = `${percentage}%`;
+}
+
+// Iniciar timer
+function startTimer() {
+ if (isRunning) return;
+
+ isRunning = true;
+ startBtn.disabled = true;
+ pauseBtn.disabled = false;
+
+ timerInterval = setInterval(() => {
+ timeLeft--;
+ updateDisplay();
+ updateProgress();
+
+ if (timeLeft <= 0) {
+ completeSession();
+ }
+ }, 1000);
+}
+
+// Pausar timer
+function pauseTimer() {
+ isRunning = false;
+ clearInterval(timerInterval);
+ startBtn.disabled = false;
+ pauseBtn.disabled = true;
+}
+
+// Resetar timer
+function resetTimer() {
+ pauseTimer();
+ timeLeft = getTimeForMode(currentMode);
+ totalTime = timeLeft;
+ updateDisplay();
+ updateProgress();
+}
+
+// Completar sessão
+function completeSession() {
+ pauseTimer();
+ playSound();
+ showNotification();
+
+ if (currentMode === 'focus') {
+ focusSessionsCompleted++;
+ focusCount.textContent = focusSessionsCompleted;
+
+ // Após 4 sessões de foco, pausa longa
+ if (focusSessionsCompleted % 4 === 0) {
+ switchMode('longBreak');
+ totalCycles++;
+ cyclesCount.textContent = totalCycles;
+ } else {
+ switchMode('shortBreak');
+ }
+ } else {
+ switchMode('focus');
+ }
+}
+
+// Trocar modo
+function switchMode(mode) {
+ currentMode = mode;
+ timeLeft = getTimeForMode(mode);
+ totalTime = timeLeft;
+ updateDisplay();
+ updateProgress();
+}
+
+// Obter tempo para o modo
+function getTimeForMode(mode) {
+ const times = {
+ focus: config.focusTime * 60,
+ shortBreak: config.shortBreakTime * 60,
+ longBreak: config.longBreakTime * 60
+ };
+ return times[mode];
+}
+
+// Tocar som de notificação
+function playSound() {
+ // Criar um beep simples usando Web Audio API
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+ const oscillator = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+
+ oscillator.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+
+ oscillator.frequency.value = 800;
+ oscillator.type = 'sine';
+
+ gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
+
+ oscillator.start(audioContext.currentTime);
+ oscillator.stop(audioContext.currentTime + 0.5);
+}
+
+// Mostrar notificação
+function showNotification() {
+ if ('Notification' in window && Notification.permission === 'granted') {
+ const messages = {
+ focus: 'Tempo de pausa! Descanse um pouco. ☕',
+ shortBreak: 'Pausa acabou! Hora de focar! 🎯',
+ longBreak: 'Pausa longa acabou! Pronto para mais? 💪'
+ };
+
+ new Notification('⏰ Pomodoro Timer', {
+ body: messages[currentMode],
+ icon: '🍅'
+ });
+ } else {
+ alert('Sessão completa! ' + (currentMode === 'focus' ? 'Hora de pausar!' : 'Hora de focar!'));
+ }
+}
+
+// Solicitar permissão para notificações
+function requestNotificationPermission() {
+ if ('Notification' in window && Notification.permission === 'default') {
+ Notification.requestPermission();
+ }
+}
+
+// Salvar configurações
+function saveSettings() {
+ config.focusTime = parseInt(focusTimeInput.value);
+ config.shortBreakTime = parseInt(shortBreakTimeInput.value);
+ config.longBreakTime = parseInt(longBreakTimeInput.value);
+
+ localStorage.setItem('pomodoroConfig', JSON.stringify(config));
+
+ if (currentMode === 'focus') {
+ resetTimer();
+ }
+}
+
+// Carregar configurações
+function loadSettings() {
+ const saved = localStorage.getItem('pomodoroConfig');
+ if (saved) {
+ const savedConfig = JSON.parse(saved);
+ config.focusTime = savedConfig.focusTime;
+ config.shortBreakTime = savedConfig.shortBreakTime;
+ config.longBreakTime = savedConfig.longBreakTime;
+
+ focusTimeInput.value = config.focusTime;
+ shortBreakTimeInput.value = config.shortBreakTime;
+ longBreakTimeInput.value = config.longBreakTime;
+ }
+}
+
+// Event Listeners
+startBtn.addEventListener('click', startTimer);
+pauseBtn.addEventListener('click', pauseTimer);
+resetBtn.addEventListener('click', resetTimer);
+
+focusTimeInput.addEventListener('change', saveSettings);
+shortBreakTimeInput.addEventListener('change', saveSettings);
+longBreakTimeInput.addEventListener('change', saveSettings);
+
+// Atalhos de teclado
+document.addEventListener('keydown', (e) => {
+ if (e.code === 'Space') {
+ e.preventDefault();
+ if (isRunning) {
+ pauseTimer();
+ } else {
+ startTimer();
+ }
+ } else if (e.code === 'KeyR') {
+ resetTimer();
+ }
+});
+
+// Inicializar
+init();
+requestNotificationPermission();
+
+console.log('🍅 Pomodoro Timer carregado!');
+console.log('Atalhos: Espaço = Iniciar/Pausar | R = Resetar');
diff --git a/iniciante/temporizador-pomodoro/script.py b/iniciante/temporizador-pomodoro/script.py
new file mode 100644
index 0000000..e66dd23
--- /dev/null
+++ b/iniciante/temporizador-pomodoro/script.py
@@ -0,0 +1,398 @@
+import tkinter as tk
+from tkinter import ttk, messagebox
+import time
+import threading
+import winsound
+import json
+import os
+
+
+class PomodoroTimer:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("🍅 Temporizador Pomodoro")
+ self.root.geometry("500x700")
+ self.root.resizable(False, False)
+ self.root.configure(bg='#f0f0f0')
+
+ # Estado
+ self.is_running = False
+ self.current_mode = 'focus' # 'focus', 'short_break', 'long_break'
+ self.focus_sessions_completed = 0
+ self.total_cycles = 0
+ self.timer_thread = None
+
+ # Configurações padrão
+ self.config = {
+ 'focus_time': 25,
+ 'short_break_time': 5,
+ 'long_break_time': 15
+ }
+
+ self.load_config()
+
+ # Tempo inicial
+ self.time_left = self.config['focus_time'] * 60
+ self.total_time = self.time_left
+
+ self.setup_ui()
+ self.update_display()
+
+ def setup_ui(self):
+ # Container principal
+ main_frame = tk.Frame(self.root, bg='#f0f0f0')
+ main_frame.pack(expand=True, fill='both', padx=20, pady=20)
+
+ # Título
+ title_label = tk.Label(
+ main_frame,
+ text="🍅 Temporizador Pomodoro",
+ font=('Arial', 24, 'bold'),
+ bg='#f0f0f0',
+ fg='#333'
+ )
+ title_label.pack(pady=(0, 20))
+
+ # Display do timer
+ timer_frame = tk.Frame(main_frame, bg='white', relief='raised', bd=2)
+ timer_frame.pack(fill='x', pady=10)
+
+ # Label do modo
+ self.mode_label = tk.Label(
+ timer_frame,
+ text="🎯 FOCO",
+ font=('Arial', 16, 'bold'),
+ bg='white',
+ fg='#667eea'
+ )
+ self.mode_label.pack(pady=(20, 10))
+
+ # Display do tempo
+ self.time_label = tk.Label(
+ timer_frame,
+ text="25:00",
+ font=('Courier New', 60, 'bold'),
+ bg='white',
+ fg='#333'
+ )
+ self.time_label.pack(pady=20)
+
+ # Barra de progresso
+ self.progress = ttk.Progressbar(
+ timer_frame,
+ length=400,
+ mode='determinate',
+ maximum=100
+ )
+ self.progress.pack(pady=(10, 20))
+ self.progress['value'] = 100
+
+ # Botões de controle
+ control_frame = tk.Frame(main_frame, bg='#f0f0f0')
+ control_frame.pack(pady=20)
+
+ self.start_btn = tk.Button(
+ control_frame,
+ text="▶ Iniciar",
+ command=self.start_timer,
+ font=('Arial', 12, 'bold'),
+ bg='#4caf50',
+ fg='white',
+ width=12,
+ height=2,
+ relief='raised',
+ cursor='hand2'
+ )
+ self.start_btn.grid(row=0, column=0, padx=5)
+
+ self.pause_btn = tk.Button(
+ control_frame,
+ text="⏸ Pausar",
+ command=self.pause_timer,
+ font=('Arial', 12, 'bold'),
+ bg='#ff9800',
+ fg='white',
+ width=12,
+ height=2,
+ relief='raised',
+ cursor='hand2',
+ state='disabled'
+ )
+ self.pause_btn.grid(row=0, column=1, padx=5)
+
+ reset_btn = tk.Button(
+ control_frame,
+ text="↻ Resetar",
+ command=self.reset_timer,
+ font=('Arial', 12, 'bold'),
+ bg='#f44336',
+ fg='white',
+ width=12,
+ height=2,
+ relief='raised',
+ cursor='hand2'
+ )
+ reset_btn.grid(row=0, column=2, padx=5)
+
+ # Estatísticas
+ stats_frame = tk.LabelFrame(
+ main_frame,
+ text="📊 Estatísticas",
+ font=('Arial', 12, 'bold'),
+ bg='white',
+ fg='#333',
+ relief='raised',
+ bd=2
+ )
+ stats_frame.pack(fill='x', pady=10)
+
+ stats_inner = tk.Frame(stats_frame, bg='white')
+ stats_inner.pack(padx=20, pady=15)
+
+ tk.Label(
+ stats_inner,
+ text="Ciclos Completos:",
+ font=('Arial', 11),
+ bg='white',
+ fg='#666'
+ ).grid(row=0, column=0, sticky='w', pady=5)
+
+ self.cycles_label = tk.Label(
+ stats_inner,
+ text="0",
+ font=('Arial', 11, 'bold'),
+ bg='white',
+ fg='#667eea'
+ )
+ self.cycles_label.grid(row=0, column=1, sticky='e', pady=5, padx=(50, 0))
+
+ tk.Label(
+ stats_inner,
+ text="Sessões de Foco:",
+ font=('Arial', 11),
+ bg='white',
+ fg='#666'
+ ).grid(row=1, column=0, sticky='w', pady=5)
+
+ self.focus_label = tk.Label(
+ stats_inner,
+ text="0",
+ font=('Arial', 11, 'bold'),
+ bg='white',
+ fg='#667eea'
+ )
+ self.focus_label.grid(row=1, column=1, sticky='e', pady=5, padx=(50, 0))
+
+ # Configurações
+ settings_frame = tk.LabelFrame(
+ main_frame,
+ text="⚙️ Configurações",
+ font=('Arial', 12, 'bold'),
+ bg='white',
+ fg='#333',
+ relief='raised',
+ bd=2
+ )
+ settings_frame.pack(fill='x', pady=10)
+
+ settings_inner = tk.Frame(settings_frame, bg='white')
+ settings_inner.pack(padx=20, pady=15)
+
+ # Tempo de foco
+ tk.Label(
+ settings_inner,
+ text="Tempo de Foco (min):",
+ font=('Arial', 10),
+ bg='white',
+ fg='#666'
+ ).grid(row=0, column=0, sticky='w', pady=5)
+
+ self.focus_time_var = tk.IntVar(value=self.config['focus_time'])
+ focus_spinbox = tk.Spinbox(
+ settings_inner,
+ from_=1,
+ to=60,
+ textvariable=self.focus_time_var,
+ width=10,
+ font=('Arial', 10),
+ command=self.save_config
+ )
+ focus_spinbox.grid(row=0, column=1, sticky='e', pady=5, padx=(20, 0))
+
+ # Pausa curta
+ tk.Label(
+ settings_inner,
+ text="Pausa Curta (min):",
+ font=('Arial', 10),
+ bg='white',
+ fg='#666'
+ ).grid(row=1, column=0, sticky='w', pady=5)
+
+ self.short_break_var = tk.IntVar(value=self.config['short_break_time'])
+ short_spinbox = tk.Spinbox(
+ settings_inner,
+ from_=1,
+ to=30,
+ textvariable=self.short_break_var,
+ width=10,
+ font=('Arial', 10),
+ command=self.save_config
+ )
+ short_spinbox.grid(row=1, column=1, sticky='e', pady=5, padx=(20, 0))
+
+ # Pausa longa
+ tk.Label(
+ settings_inner,
+ text="Pausa Longa (min):",
+ font=('Arial', 10),
+ bg='white',
+ fg='#666'
+ ).grid(row=2, column=0, sticky='w', pady=5)
+
+ self.long_break_var = tk.IntVar(value=self.config['long_break_time'])
+ long_spinbox = tk.Spinbox(
+ settings_inner,
+ from_=1,
+ to=60,
+ textvariable=self.long_break_var,
+ width=10,
+ font=('Arial', 10),
+ command=self.save_config
+ )
+ long_spinbox.grid(row=2, column=1, sticky='e', pady=5, padx=(20, 0))
+
+ def format_time(self, seconds):
+ """Formata segundos em MM:SS"""
+ mins = seconds // 60
+ secs = seconds % 60
+ return f"{mins:02d}:{secs:02d}"
+
+ def update_display(self):
+ """Atualiza o display do tempo"""
+ self.time_label.config(text=self.format_time(self.time_left))
+
+ # Atualizar label do modo
+ mode_labels = {
+ 'focus': '🎯 FOCO',
+ 'short_break': '☕ PAUSA CURTA',
+ 'long_break': '🌴 PAUSA LONGA'
+ }
+ self.mode_label.config(text=mode_labels[self.current_mode])
+
+ # Atualizar barra de progresso
+ if self.total_time > 0:
+ percentage = (self.time_left / self.total_time) * 100
+ self.progress['value'] = percentage
+
+ def start_timer(self):
+ """Inicia o timer"""
+ if not self.is_running:
+ self.is_running = True
+ self.start_btn.config(state='disabled')
+ self.pause_btn.config(state='normal')
+
+ self.timer_thread = threading.Thread(target=self.run_timer, daemon=True)
+ self.timer_thread.start()
+
+ def run_timer(self):
+ """Loop principal do timer"""
+ while self.is_running and self.time_left > 0:
+ time.sleep(1)
+ self.time_left -= 1
+ self.root.after(0, self.update_display)
+
+ if self.time_left <= 0 and self.is_running:
+ self.root.after(0, self.complete_session)
+
+ def pause_timer(self):
+ """Pausa o timer"""
+ self.is_running = False
+ self.start_btn.config(state='normal')
+ self.pause_btn.config(state='disabled')
+
+ def reset_timer(self):
+ """Reseta o timer"""
+ self.pause_timer()
+ self.time_left = self.get_time_for_mode(self.current_mode)
+ self.total_time = self.time_left
+ self.update_display()
+
+ def complete_session(self):
+ """Completa uma sessão"""
+ self.pause_timer()
+ self.play_sound()
+
+ if self.current_mode == 'focus':
+ self.focus_sessions_completed += 1
+ self.focus_label.config(text=str(self.focus_sessions_completed))
+
+ # Após 4 sessões, pausa longa
+ if self.focus_sessions_completed % 4 == 0:
+ self.switch_mode('long_break')
+ self.total_cycles += 1
+ self.cycles_label.config(text=str(self.total_cycles))
+ messagebox.showinfo("🌴 Pausa Longa!", "Você completou 4 sessões! Hora de uma pausa longa!")
+ else:
+ self.switch_mode('short_break')
+ messagebox.showinfo("☕ Pausa!", "Sessão de foco completa! Hora de pausar!")
+ else:
+ self.switch_mode('focus')
+ messagebox.showinfo("🎯 Foco!", "Pausa terminada! Hora de focar!")
+
+ def switch_mode(self, mode):
+ """Troca o modo do timer"""
+ self.current_mode = mode
+ self.time_left = self.get_time_for_mode(mode)
+ self.total_time = self.time_left
+ self.update_display()
+
+ def get_time_for_mode(self, mode):
+ """Retorna o tempo em segundos para o modo"""
+ times = {
+ 'focus': self.config['focus_time'] * 60,
+ 'short_break': self.config['short_break_time'] * 60,
+ 'long_break': self.config['long_break_time'] * 60
+ }
+ return times[mode]
+
+ def play_sound(self):
+ """Toca som de notificação"""
+ try:
+ # Beep do Windows
+ winsound.Beep(800, 500)
+ except:
+ print("\a") # Beep alternativo
+
+ def save_config(self):
+ """Salva configurações"""
+ self.config['focus_time'] = self.focus_time_var.get()
+ self.config['short_break_time'] = self.short_break_var.get()
+ self.config['long_break_time'] = self.long_break_var.get()
+
+ try:
+ with open('pomodoro_config.json', 'w') as f:
+ json.dump(self.config, f)
+ except:
+ pass
+
+ if self.current_mode == 'focus':
+ self.reset_timer()
+
+ def load_config(self):
+ """Carrega configurações"""
+ try:
+ if os.path.exists('pomodoro_config.json'):
+ with open('pomodoro_config.json', 'r') as f:
+ self.config = json.load(f)
+ except:
+ pass
+
+
+def main():
+ root = tk.Tk()
+ app = PomodoroTimer(root)
+ root.mainloop()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/iniciante/temporizador-pomodoro/style.css b/iniciante/temporizador-pomodoro/style.css
new file mode 100644
index 0000000..8a42531
--- /dev/null
+++ b/iniciante/temporizador-pomodoro/style.css
@@ -0,0 +1,210 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 20px;
+}
+
+.container {
+ background: white;
+ border-radius: 20px;
+ padding: 40px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+}
+
+h1 {
+ text-align: center;
+ color: #333;
+ margin-bottom: 30px;
+ font-size: 2rem;
+}
+
+.timer-display {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.mode-label {
+ font-size: 1.2rem;
+ color: #667eea;
+ font-weight: bold;
+ margin-bottom: 10px;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+}
+
+.time {
+ font-size: 4rem;
+ font-weight: bold;
+ color: #333;
+ margin: 20px 0;
+ font-family: 'Courier New', monospace;
+}
+
+.progress-bar {
+ width: 100%;
+ height: 10px;
+ background: #e0e0e0;
+ border-radius: 10px;
+ overflow: hidden;
+ margin-top: 20px;
+}
+
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+ width: 100%;
+ transition: width 0.3s ease;
+ border-radius: 10px;
+}
+
+.controls {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ margin-bottom: 30px;
+}
+
+.btn {
+ padding: 12px 24px;
+ font-size: 1rem;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn-start {
+ background: #4caf50;
+ color: white;
+}
+
+.btn-pause {
+ background: #ff9800;
+ color: white;
+}
+
+.btn-reset {
+ background: #f44336;
+ color: white;
+}
+
+.stats {
+ background: #f5f5f5;
+ padding: 20px;
+ border-radius: 10px;
+ margin-bottom: 20px;
+}
+
+.stat-item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ font-size: 1rem;
+}
+
+.stat-item:last-child {
+ margin-bottom: 0;
+}
+
+.stat-label {
+ color: #666;
+}
+
+.stat-value {
+ font-weight: bold;
+ color: #667eea;
+ font-size: 1.2rem;
+}
+
+.settings {
+ background: #f9f9f9;
+ padding: 20px;
+ border-radius: 10px;
+}
+
+.settings h3 {
+ color: #333;
+ margin-bottom: 15px;
+ font-size: 1.2rem;
+}
+
+.setting-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.setting-item:last-child {
+ margin-bottom: 0;
+}
+
+.setting-item label {
+ color: #666;
+ font-size: 0.95rem;
+}
+
+.setting-item input {
+ width: 80px;
+ padding: 8px;
+ border: 2px solid #e0e0e0;
+ border-radius: 5px;
+ font-size: 1rem;
+ text-align: center;
+}
+
+.setting-item input:focus {
+ outline: none;
+ border-color: #667eea;
+}
+
+@media (max-width: 600px) {
+ .container {
+ padding: 20px;
+ }
+
+ h1 {
+ font-size: 1.5rem;
+ }
+
+ .time {
+ font-size: 3rem;
+ }
+
+ .controls {
+ flex-direction: column;
+ }
+
+ .btn {
+ width: 100%;
+ }
+}
\ No newline at end of file