diff --git a/global.css b/global.css
index 29c5c72..6319bc1 100644
--- a/global.css
+++ b/global.css
@@ -37,7 +37,14 @@ a:hover, svg:hover {
color: #c099ff;
}
-::selection {
- background-color: #c099ff;
- color: #2a2a2a;
-}
\ No newline at end of file
+.card {
+ background-color: #252525;
+ padding: 20px;
+ border-radius: 10px;
+ width: 600px !important;
+ text-align: center;
+}
+
+.shadow {
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+}
diff --git a/ip/index.html b/ip/index.html
new file mode 100644
index 0000000..44803c0
--- /dev/null
+++ b/ip/index.html
@@ -0,0 +1,21 @@
+
+
+
+ IP Lookup
+
+
+
+
+
+
+ IP Lookup
+
+
+
+
Your IP: 69.420.69.69
+
+
+
+
+
+
diff --git a/ip/index.js b/ip/index.js
new file mode 100644
index 0000000..470023d
--- /dev/null
+++ b/ip/index.js
@@ -0,0 +1,48 @@
+const lookupElem = document.getElementById('lookupElem');
+const yourIp = document.getElementById('your-ip');
+
+function getFlagEmoji(countryCode) {
+ return countryCode
+ .toUpperCase()
+ .replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt()));
+}
+
+function fetchIp() {
+ const ip = document.getElementById("ipInput").value;
+ fetch(`https://ipapi.co/${ip}/json/`)
+ .then(response => response.json())
+ .then(data => {
+ const flag = getFlagEmoji(data.country_code || '');
+ const table = `
+
+ IP | ${data.ip} |
+ City | ${data.city} |
+ Postal Code | ${data.postal} |
+ Region | ${data.region} |
+ Country | ${flag} ${data.country_name} (${data.country_code}) |
+ Timezone | ${data.timezone} |
+ Organization | ${data.org} |
+ Coordinates | ${data.latitude}, ${data.longitude} |
+
+ `;
+ document.getElementById("output").innerHTML = table;
+ })
+ .catch(err => {
+ document.getElementById("output").textContent = "Error: " + err;
+ });
+}
+
+function getYour() {
+ fetch(`https://ipapi.co/json/?ip=ipv4`)
+ .then(response => response.json())
+ .then(data => {
+ yourIp.innerHTML = data.ip;
+ })
+ .catch(err => {
+ yourIp.textContent = "Error: " + err;
+ });
+}
+
+window.addEventListener('load', getYour);
+
+lookupElem.addEventListener('click', fetchIp);
diff --git a/ip/style.css b/ip/style.css
new file mode 100644
index 0000000..70199f6
--- /dev/null
+++ b/ip/style.css
@@ -0,0 +1,104 @@
+@import url(/global.css);
+
+h1 {
+ font-size: 2rem;
+ margin-bottom: 30px;
+}
+
+input[type="text"] {
+ background: #1e1e1e;
+ border: 1px solid #444;
+ color: #f0f0f0;
+ padding: 12px 15px;
+ border-radius: 8px;
+ font-size: 1rem;
+ width: 70%;
+ margin-right: 10px;
+ transition: border-color 0.2s;
+ outline: none;
+ z-index: 2;
+}
+
+button {
+ background: #c099ff;
+ color: #1e1e1e;
+ border: none;
+ padding: 12px 20px;
+ font-size: 1rem;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 0.2s;
+ z-index: 2;
+}
+
+button:hover {
+ background: #a875ff;
+}
+
+#output {
+ margin-top: 25px;
+ border-radius: 10px;
+ width: 100%;
+ max-width: 640px;
+}
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+th, td {
+ padding: 10px 14px;
+ border: 2px solid #2a2a2a;
+ background-color: #1f1f1f;
+}
+
+th:hover,
+td:hover {
+ background-color: #232323;
+}
+
+th {
+ text-align: left;
+}
+
+.your {
+ margin-top: 15px;
+}
+
+#your-ip:not(:hover) {
+ filter: blur(10px);
+}
+
+.tooltip {
+ display: flex;
+ justify-content: center;
+ position: relative;
+}
+
+.tooltip::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ bottom: 125%;
+ background-color: #2a2a2a;
+ border: 2px solid rgba(150, 150, 150, 0.1);
+ color: #fff;
+ padding: 5px 10px;
+ border-radius: 5px;
+ font-size: 14px;
+ white-space: nowrap;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.3s, visibility 0.3s;
+ cursor: default;
+}
+
+.tooltip:hover::after {
+ opacity: 1;
+ visibility: visible;
+}
diff --git a/morse/index.html b/morse/index.html
new file mode 100644
index 0000000..6c7ed6d
--- /dev/null
+++ b/morse/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Morse Translator
+
+
+
+
+
+
Morse Translator
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/morse/index.js b/morse/index.js
new file mode 100644
index 0000000..ff6cc2d
--- /dev/null
+++ b/morse/index.js
@@ -0,0 +1,146 @@
+const morseElem = document.getElementById('translate-to-morse');
+const textElem = document.getElementById('translate-to-text');
+const soundElem = document.getElementById('play-sound');
+const exportElem = document.getElementById('export');
+
+const morseCode = {
+ 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----', ' ': '/'
+};
+
+const morseToText = Object.fromEntries(Object.entries(morseCode).map(([key, value]) => [value, key]));
+
+function translateToMorse() {
+ const text = document.getElementById('textInput').value.toUpperCase();
+ const morse = text.split('').map(char => morseCode[char] || '').join(' ');
+ document.getElementById('morseOutput').value = morse;
+}
+
+function translateToText() {
+ const morse = document.getElementById('morseOutput').value.trim();
+ const text = morse.split(' ').map(code => morseToText[code] || '').join('');
+ document.getElementById('textInput').value = text;
+}
+
+function playMorseSound() {
+ const morse = document.getElementById('morseOutput').value.trim();
+ let audioContext = new (window.AudioContext || window.webkitAudioContext)();
+ let dotDuration = 200;
+ let dashDuration = dotDuration * 3;
+ let gapDuration = dotDuration;
+
+ function playTone(freq, duration, volume) {
+ const oscillator = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+ oscillator.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+ oscillator.type = 'sine';
+ oscillator.frequency.setValueAtTime(freq, audioContext.currentTime);
+ gainNode.gain.setValueAtTime(volume, audioContext.currentTime);
+ oscillator.start();
+ oscillator.stop(audioContext.currentTime + duration / 1000);
+ }
+
+ let position = 0;
+ function playMorseCode() {
+ if (position < morse.length) {
+ const symbol = morse[position];
+
+ if (symbol === '.') {
+ playTone(1000, dotDuration, 0.1);
+ position++;
+ setTimeout(playMorseCode, dotDuration + gapDuration);
+ } else if (symbol === '-') {
+ playTone(1000, dashDuration, 0.1);
+ position++;
+ setTimeout(playMorseCode, dashDuration + gapDuration);
+ } else if (symbol === ' ') {
+ position++;
+ setTimeout(playMorseCode, gapDuration);
+ }
+ }
+ }
+
+ playMorseCode();
+}
+
+function exportSound() {
+ const morse = document.getElementById('morseOutput').value.trim();
+ let dotDuration = 200;
+ let dashDuration = dotDuration * 3;
+ let gapDuration = dotDuration;
+
+ let sampleRate = 44100;
+ let samples = [];
+
+ function addSilence(duration) {
+ let count = (sampleRate * duration) / 1000;
+ for (let i = 0; i < count; i++) {
+ samples.push(0);
+ }
+ }
+
+ function addTone(freq, duration) {
+ let count = (sampleRate * duration) / 1000;
+ for (let i = 0; i < count; i++) {
+ let t = i / sampleRate;
+ samples.push(Math.sin(2 * Math.PI * freq * t));
+ }
+ }
+
+ for (let symbol of morse) {
+ if (symbol === '.') {
+ addTone(1000, dotDuration);
+ addSilence(gapDuration);
+ } else if (symbol === '-') {
+ addTone(1000, dashDuration);
+ addSilence(gapDuration);
+ } else if (symbol === ' ') {
+ addSilence(gapDuration);
+ }
+ }
+
+ const buffer = new ArrayBuffer(44 + samples.length * 2);
+ const view = new DataView(buffer);
+
+ function writeString(offset, string) {
+ for (let i = 0; i < string.length; i++) {
+ view.setUint8(offset + i, string.charCodeAt(i));
+ }
+ }
+
+ // i have no idea what this shit is or how it works, but it does :rofl:
+ writeString(0, 'RIFF');
+ view.setUint32(4, 36 + samples.length * 2, true);
+ writeString(8, 'WAVE');
+ writeString(12, 'fmt ');
+ view.setUint32(16, 16, true);
+ view.setUint16(20, 1, true);
+ view.setUint16(22, 1, true);
+ view.setUint32(24, sampleRate, true);
+ view.setUint32(28, sampleRate * 2, true);
+ view.setUint16(32, 2, true);
+ view.setUint16(34, 16, true);
+ writeString(36, 'data');
+ view.setUint32(40, samples.length * 2, true);
+
+ for (let i = 0; i < samples.length; i++) {
+ let s = Math.max(-1, Math.min(1, samples[i]));
+ view.setInt16(44 + i * 2, s * 32767, true);
+ }
+
+ const blob = new Blob([view], { type: 'audio/wav' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'morse-code.wav';
+ a.click();
+}
+
+
+morseElem.addEventListener('click', translateToMorse);
+
+textElem.addEventListener('click', translateToText);
+
+soundElem.addEventListener('click', playMorseSound);
+
+exportElem.addEventListener('click', exportSound);
diff --git a/morse/style.css b/morse/style.css
new file mode 100644
index 0000000..479d68d
--- /dev/null
+++ b/morse/style.css
@@ -0,0 +1,33 @@
+@import url(/global.css);
+
+.container {
+ text-align: center;
+ padding: 20px;
+ border-radius: 20px;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.7);
+}
+
+textarea, input, button {
+ width: 70%;
+ margin-top: 10px;
+ padding: 10px;
+ border-radius: 10px;
+ border: none;
+ background-color: #333;
+ color: #f0f0f0;
+}
+
+textarea {
+ resize: none;
+ margin-right: 10px;
+}
+
+button {
+ cursor: pointer;
+ background-color: #444;
+}
+
+button:hover {
+ background-color: #555;
+}
+
diff --git a/portfolio/style.css b/portfolio/style.css
index 428fe5d..065b6fe 100644
--- a/portfolio/style.css
+++ b/portfolio/style.css
@@ -184,7 +184,6 @@ div[class="name-percent-container"] > img.image {
}
.tooltip {
- cursor: help;
display: flex;
justify-content: center;
position: relative;