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
+
Palavras
+
+ +
+
🔤
+
0
+
Caracteres
+
+ +
+
📄
+
0
+
Sem Espaços
+
+ +
+
📋
+
0
+
Linhas
+
+ +
+
📖
+
0
+
Parágrafos
+
+ +
+
⏱️
+
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

+ +
+
Foco
+
25:00
+
+
+
+
+ +
+ + + +
+ +
+
+ Ciclos Completos: + 0 +
+
+ Sessões de Foco: + 0 +
+
+ +
+

⚙️ Configurações

+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + \ 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