LInT / src /streamlit_app.py
aazzrrooddeell's picture
Update src/streamlit_app.py
334e907 verified
Raw
History Blame Contribute Delete
56.7 kB
import streamlit as st
import streamlit.components.v1 as components
import random
import re
import time
import html as html_lib
import requests
from datetime import datetime
# ============================================================
# PAGE CONFIGURATION
# ============================================================
st.set_page_config(
page_title="LinkedIn Translator | Normal English → LinkedIn Language",
page_icon="💼",
layout="wide",
initial_sidebar_state="expanded",
)
# ============================================================
# SESSION STATE INITIALIZATION
# ============================================================
if "history" not in st.session_state:
st.session_state.history = []
if "theme" not in st.session_state:
st.session_state.theme = "dark"
if "translation_count" not in st.session_state:
st.session_state.translation_count = 0
if "last_output" not in st.session_state:
st.session_state.last_output = ""
if "last_input" not in st.session_state:
st.session_state.last_input = ""
if "last_style" not in st.session_state:
st.session_state.last_style = ""
if "was_spun" not in st.session_state:
st.session_state.was_spun = False
if "last_ai_used" not in st.session_state:
st.session_state.last_ai_used = False
# ============================================================
# STYLE DEFINITIONS
# ============================================================
STYLES = {
"Corporate Buzzword 🏢": {
"key": "corporate",
"desc": "Synergy, leverage, and moving the needle. Sound important while saying nothing.",
"color": "#0A66C2",
},
"Humble Brag 😌": {
"key": "humblebrag",
"desc": "Humbled. Honored. Blessed. Modesty is just another word for showing off.",
"color": "#E7A33E",
},
"Hustle Culture 💪": {
"key": "hustle",
"desc": "5AM wakeups, no days off, and questionable life choices.",
"color": "#FF6B35",
},
"Thought Leader 🧠": {
"key": "thoughtleader",
"desc": "Hot takes nobody asked for. Let that sink in.",
"color": "#7B68EE",
},
"Tech Bro 🚀": {
"key": "techbro",
"desc": "Shipping, iterating, and disrupting. What's your stack?",
"color": "#00D4AA",
},
"Motivational Speaker 🔥": {
"key": "motivational",
"desc": "REMINDER: You are a warrior. Act like it.",
"color": "#FF4500",
},
"Maximum Cringe 😬": {
"key": "maxcringe",
"desc": "All LinkedIn stereotypes combined. You've been warned.",
"color": "#FF1493",
},
}
EXAMPLE_INPUTS = [
"I got a new job",
"I lost my job",
"I learned Python today",
"I woke up early today",
"I made a mistake at work",
"We had a meeting about the project",
"I had coffee this morning",
"I failed my interview",
]
# ============================================================
# THEMING / CSS
# ============================================================
def inject_css(theme):
if theme == "dark":
css = """
<style>
.stApp {
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%) !important;
}
.stSidebar > div:first-child {
background: linear-gradient(180deg, #0d1117 0%, #1c2333 100%) !important;
}
.main-title {
background: linear-gradient(90deg, #0A66C2, #7B68EE, #FF6B35);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 3rem !important;
font-weight: 800 !important;
text-align: center;
margin-bottom: 0 !important;
}
.subtitle {
color: #8b949e !important;
text-align: center;
font-size: 1.1rem !important;
margin-bottom: 2rem !important;
}
.input-card, .output-card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 4px 24px rgba(0,0,0,0.4);
}
.input-card { border-left: 4px solid #8b949e; }
.output-card { border-left: 4px solid #0A66C2; }
.card-label {
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 0.75rem;
}
.input-label { color: #8b949e; }
.output-label { color: #0A66C2; }
.stat-box {
background: #21262d;
border: 1px solid #30363d;
border-radius: 12px;
padding: 0.75rem 1rem;
text-align: center;
}
.stat-value { font-size: 1.5rem; font-weight: 800; color: #0A66C2; }
.stat-label { font-size: 0.7rem; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; }
.cringe-bar-bg { background: #21262d; border-radius: 10px; height: 14px; overflow: hidden; }
.cringe-bar-fill { height: 100%; border-radius: 10px; transition: width 0.8s ease; }
.history-item {
background: #21262d;
border: 1px solid #30363d;
border-radius: 10px;
padding: 0.75rem;
margin-bottom: 0.5rem;
font-size: 0.82rem;
color: #c9d1d9;
}
.style-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 700;
color: white;
}
.divider-line { border: none; height: 1px; background: #30363d; margin: 1rem 0; }
.output-display {
background: #0d1117;
border: 1px solid #30363d;
border-radius: 12px;
padding: 1rem;
min-height: 150px;
color: #e6edf3;
font-size: 0.95rem;
line-height: 1.7;
white-space: pre-wrap;
word-wrap: break-word;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.output-empty {
background: #0d1117;
border: 1px dashed #30363d;
border-radius: 12px;
padding: 1rem;
min-height: 150px;
color: #484f58;
font-size: 0.95rem;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.spin-tag {
display: inline-block;
background: #238636;
color: white;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.ai-tag {
display: inline-block;
background: #7B68EE;
color: white;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.rule-tag {
display: inline-block;
background: #484f58;
color: #c9d1d9;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.tags-container {
margin-bottom: 8px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.stTextArea > div > div > textarea {
background-color: #0d1117 !important;
color: #e6edf3 !important;
border-color: #30363d !important;
border-radius: 10px !important;
}
.stTextArea > div > div > textarea:focus {
border-color: #0A66C2 !important;
box-shadow: 0 0 0 2px rgba(10,102,194,0.3) !important;
}
.stButton > button {
border-radius: 10px !important;
font-weight: 600 !important;
transition: all 0.2s !important;
}
.stButton > button:hover {
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(10,102,194,0.4) !important;
}
.stSelectbox > div > div { background-color: #0d1117 !important; color: #e6edf3 !important; }
</style>
"""
else:
css = """
<style>
.stApp {
background: linear-gradient(135deg, #f0f2f5 0%, #e8ecf1 100%) !important;
}
.stSidebar > div:first-child {
background: linear-gradient(180deg, #ffffff 0%, #f6f8fa 100%) !important;
}
.main-title {
background: linear-gradient(90deg, #0A66C2, #7B68EE, #FF6B35);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 3rem !important;
font-weight: 800 !important;
text-align: center;
margin-bottom: 0 !important;
}
.subtitle {
color: #5e5e5e !important;
text-align: center;
font-size: 1.1rem !important;
margin-bottom: 2rem !important;
}
.input-card, .output-card {
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}
.input-card { border-left: 4px solid #8b949e; }
.output-card { border-left: 4px solid #0A66C2; }
.card-label {
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 0.75rem;
}
.input-label { color: #57606a; }
.output-label { color: #0A66C2; }
.stat-box {
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 12px;
padding: 0.75rem 1rem;
text-align: center;
}
.stat-value { font-size: 1.5rem; font-weight: 800; color: #0A66C2; }
.stat-label { font-size: 0.7rem; color: #57606a; text-transform: uppercase; letter-spacing: 1px; }
.cringe-bar-bg { background: #e1e4e8; border-radius: 10px; height: 14px; overflow: hidden; }
.cringe-bar-fill { height: 100%; border-radius: 10px; transition: width 0.8s ease; }
.history-item {
background: #ffffff;
border: 1px solid #d0d7de;
border-radius: 10px;
padding: 0.75rem;
margin-bottom: 0.5rem;
font-size: 0.82rem;
color: #24292f;
}
.style-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 700;
color: white;
}
.divider-line { border: none; height: 1px; background: #d0d7de; margin: 1rem 0; }
.output-display {
background: #f6f8fa;
border: 1px solid #d0d7de;
border-radius: 12px;
padding: 1rem;
min-height: 150px;
color: #24292f;
font-size: 0.95rem;
line-height: 1.7;
white-space: pre-wrap;
word-wrap: break-word;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.output-empty {
background: #f6f8fa;
border: 1px dashed #d0d7de;
border-radius: 12px;
padding: 1rem;
min-height: 150px;
color: #8b949e;
font-size: 0.95rem;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.spin-tag {
display: inline-block;
background: #1a7f37;
color: white;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.ai-tag {
display: inline-block;
background: #7B68EE;
color: white;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.rule-tag {
display: inline-block;
background: #57606a;
color: white;
padding: 2px 10px;
border-radius: 8px;
font-size: 0.65rem;
font-weight: 700;
vertical-align: middle;
}
.tags-container {
margin-bottom: 8px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.stTextArea > div > div > textarea {
background-color: #ffffff !important;
color: #24292f !important;
border-color: #d0d7de !important;
border-radius: 10px !important;
}
.stTextArea > div > div > textarea:focus {
border-color: #0A66C2 !important;
box-shadow: 0 0 0 2px rgba(10,102,194,0.3) !important;
}
.stButton > button {
border-radius: 10px !important;
font-weight: 600 !important;
transition: all 0.2s !important;
}
.stButton > button:hover {
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(10,102,194,0.3) !important;
}
</style>
"""
st.markdown(css, unsafe_allow_html=True)
# ============================================================
# POSITIVITY SPIN LAYER
# ============================================================
POSITIVITY_SPINS = [
(r'\bI lost my job\b', "I've embraced a new chapter in my career journey"),
(r'\bI got fired\b', "I was given the freedom to pursue bigger things"),
(r'\bI was fired\b', "I was given the freedom to pursue bigger things"),
(r'\bI got laid off\b', "I was presented with an unexpected career pivot"),
(r'\bI was laid off\b', "I was presented with an unexpected career pivot"),
(r'\bI was let go\b', "I was released to explore new horizons"),
(r'\bI was made redundant\b', "I was offered a clean slate to reinvent myself"),
(r'\bI was terminated\b', "I was given a bold opportunity to start fresh"),
(r'\bI\'m unemployed\b', "I\'m in between exciting opportunities"),
(r'\bunemployed\b', "between opportunities"),
(r'\bI got downsized\b', "I was given the gift of a fresh start"),
(r'\bI quit my job\b', "I courageously chose to bet on myself"),
(r'\bI resigned\b', "I boldly decided it was time for my next act"),
(r'\bI walked out\b', "I took a bold stand for my values"),
(r'\bI failed\b', "I discovered a powerful lesson"),
(r'\bI failed at\b', "I learned immensely from"),
(r'\bI failed my\b', "I had a growth experience with my"),
(r'\bfailure\b', "stepping stone"),
(r'\bI made a mistake\b', "I had a breakthrough learning moment"),
(r'\bI messed up\b', "I found an unexpected growth opportunity"),
(r'\bI screwed up\b', "I stumbled onto a valuable lesson"),
(r'\bI was wrong\b', "I evolved my perspective"),
(r'\bI ruined\b', "I transformed"),
(r'\bI gave up\b', "I courageously redirected my energy"),
(r'\bI was rejected\b', "I was redirected toward something better"),
(r'\bI got rejected\b', "I was protected from the wrong path"),
(r'\bI got turned down\b', "I was being saved for the right opportunity"),
(r'\bI didn\'t get the job\b', "The right role is still finding me"),
(r'\bI didn\'t get accepted\b', "I was being guided to my true path"),
(r'\bI failed my interview\b', "My interview became a masterclass in growth"),
(r'\brejected\b', "redirected"),
(r'\bI\'m struggling\b', "I\'m in the crucible that forges future leaders"),
(r'\bI\'m stuck\b', "I\'m in a season of preparation for something great"),
(r'\bI\'m lost\b', "I\'m exploring uncharted territory with curiosity"),
(r'\bI\'m confused\b', "I\'m navigating a beautiful period of discovery"),
(r'\bI\'m overwhelmed\b', "I\'m being stretched into the leader I\'m meant to be"),
(r'\bI\'m stressed\b', "I\'m being forged in the fire of purpose"),
(r'\bI\'m exhausted\b', "I\'m pouring everything into what matters"),
(r'\bI\'m tired\b', "I\'m investing my energy in meaningful work"),
(r'\bI\'m burned out\b', "I\'m taking a well-deserved season of renewal"),
(r'\bI\'m broken\b', "I\'m being rebuilt stronger than ever"),
(r'\bI\'m sad\b', "I\'m processing deep emotions that make me human"),
(r'\bI\'m depressed\b', "I\'m navigating a chapter that will inspire others someday"),
(r'\bI\'m anxious\b', "I\'m being called to trust the process"),
(r'\bI\'m lonely\b', "I\'m in a sacred season of self-discovery"),
(r'\bI can\'t find a job\b', "I\'m being selective about my next career move"),
(r'\bI can\'t do this\b', "I\'m being challenged to grow beyond my comfort zone"),
(r'\bI can\'t\b', "I\'m still learning to"),
(r'\bnothing is working\b', "I\'m in the messy middle of my breakthrough"),
(r'\bnothing works\b', "every attempt brings me closer to the answer"),
(r'\bI hate my job\b', "I\'m being called to find work that aligns with my purpose"),
(r'\bI hate\b', "I\'m deeply motivated to transform"),
(r'\bI don\'t like\b', "I\'m inspired to improve"),
(r'\bI\'m broke\b', "I\'m investing in experiences over material things"),
(r'\bI have no money\b', "I\'m resource-rich in ways that matter most"),
(r'\bI\'m poor\b', "I\'m building wealth in character and resilience"),
(r'\bI was demoted\b', "I was given the chance to reconnect with the frontline"),
(r'\bI got demoted\b', "I was invited back to where the real impact happens"),
(r'\bI got dumped\b', "I was freed to focus on becoming my best self"),
(r'\bwe broke up\b', "we each graduated to our next chapter of growth"),
(r'\bterrible\b', "challenging yet transformative"),
(r'\bhorrible\b', "an invitation to grow"),
(r'\bawful\b', "a catalyst for change"),
(r'\bworst\b', "most defining"),
(r'\bdisaster\b', "opportunity in disguise"),
(r'\bcatastrophe\b', "turning point"),
(r'\bnightmare\b', "defining challenge that shaped me"),
(r'\bproblem\b', "opportunity"),
(r'\bissue\b', "growth area"),
(r'\bweakness\b', "opportunity for development"),
]
def apply_positivity_spin(text):
original = text
for pattern, replacement in POSITIVITY_SPINS:
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
was_spun = (text != original)
return text, was_spun
# ============================================================
# TRANSLATION ENGINE — RULE-BASED
# ============================================================
def apply_replacements(text, style):
universal = [
(r'\bvery good\b', 'absolutely phenomenal'),
(r'\bvery bad\b', 'a tremendous growth opportunity'),
(r'\bworked hard\b', 'poured my heart and soul'),
(r'\bsuccessful\b', 'wildly successful'),
]
for pattern, repl in universal:
text = re.sub(pattern, repl, text, flags=re.IGNORECASE)
all_replacements = {
"corporate": [
(r'\bjob\b', 'role within the organizational ecosystem'),
(r'\bwork\b', 'value-adding initiative'),
(r'\bmeeting\b', 'strategic alignment session'),
(r'\bhelp\b', 'provide cross-functional bandwidth for'),
(r'\bidea\b', 'actionable insight with measurable ROI'),
(r'\bplan\b', 'strategic roadmap with key milestones'),
(r'\bproject\b', 'flagship initiative'),
(r'\bteam\b', 'high-performing squad'),
(r'\bboss\b', 'key stakeholder'),
(r'\bchange\b', 'paradigm shift'),
(r'\bthink\b', 'ideate'),
(r'\buse\b', 'leverage'),
(r'\bstart\b', 'kick off'),
(r'\bimprove\b', 'move the needle on'),
(r'\btalk\b', 'circle back on'),
(r'\blearn\b', 'deep dive into'),
(r'\bmake\b', 'operationalize'),
(r'\bimportant\b', 'mission-critical'),
(r'\bfast\b', 'with velocity and agility'),
(r'\bbig\b', 'at scale'),
(r'\bnew\b', 'net-new'),
(r'\btogether\b', 'synergistically'),
(r'\bgood\b', 'high-impact'),
(r'\bdo\b', 'execute against'),
(r'\bneed\b', 'have identified a requirement for'),
(r'\btry\b', 'explore the feasibility of'),
(r'\bsend\b', 'cascade'),
(r'\bfix\b', 'remediate'),
(r'\bcheck\b', 'socialize'),
],
"humblebrag": [
(r'\bI got\b', 'I was humbled and honored to receive'),
(r'\bI achieved\b', 'I was blessed to achieve'),
(r'\bI won\b', "By God's grace, I was awarded"),
(r'\bI was promoted\b', 'I was graciously entrusted with an expanded role'),
(r'\bgood\b', 'beyond what I deserve'),
(r'\bhired\b', 'welcomed into an incredible family'),
(r'\bpromoted\b', 'given an opportunity I scarcely deserve'),
(r'\baward\b', 'a recognition that left me speechless'),
(r'\bexcited\b', 'humbled beyond words'),
(r'\bproud\b', 'overcome with gratitude'),
(r'\bhappy\b', 'blessed beyond measure'),
(r'\bsuccessful\b', 'overwhelmed by the kindness of the universe'),
],
"hustle": [
(r'\bwoke up\b', 'was already grinding while others were sleeping'),
(r'\bworked\b', 'was putting in the work'),
(r'\bsleep\b', 'sleep is for the weak'),
(r'\brest\b', "rest? Never heard of her"),
(r'\bjob\b', 'grind'),
(r'\bwork\b', 'the hustle'),
(r'\beffort\b', 'relentless dedication'),
(r'\bgood\b', 'next level'),
(r'\bsuccess\b', 'the result of outworking everyone'),
(r'\bhard\b', 'the price of greatness'),
(r'\bfailed\b', 'one step closer to the goal'),
(r'\btrying\b', 'committing 110%'),
(r'\bday off\b', "what's that?"),
(r'\bweekend\b', 'just another opportunity to grind'),
],
"thoughtleader": [
(r'\bI think\b', 'My contrarian take:'),
(r'\bI believe\b', "Here's the truth nobody wants to hear:"),
(r'\bI learned\b', 'What I discovered will change how you see everything:'),
(r'\bI found\b', 'The insight that changed my perspective:'),
(r'\bimportant\b', 'the single most overlooked truth'),
(r'\bgood\b', 'what the top 1% understand'),
(r'\bpeople\b', 'most people'),
(r'\badvice\b', 'the uncomfortable truth'),
],
"techbro": [
(r'\bmade\b', 'shipped'),
(r'\bbuilt\b', 'shipped'),
(r'\bcreated\b', 'shipped'),
(r'\bstarted\b', 'launched my MVP'),
(r'\blearned\b', 'leveled up in'),
(r'\bjob\b', 'role in the trenches'),
(r'\bwork\b', "what I'm building"),
(r'\bproject\b', "side project that's about to be huge"),
(r'\bidea\b', 'the next big thing'),
(r'\bteam\b', 'my co-founders and I'),
(r'\bcompany\b', 'my startup'),
(r'\bmoney\b', 'runway'),
(r'\bgood\b', 'absolutely cracked'),
(r'\bfast\b', 'at breakneck speed'),
(r'\bimprove\b', 'iterate on'),
(r'\bchange\b', 'disrupt'),
(r'\bplan\b', 'product roadmap'),
(r'\bnew\b', 'net-new'),
],
"motivational": [
(r'\bgood\b', 'UNSTOPPABLE'),
(r'\bhard\b', 'THE FIRE THAT FORGES CHAMPIONS'),
(r'\btired\b', 'RECHARGING FOR GREATNESS'),
(r'\bscared\b', 'ABOUT TO DO SOMETHING BRAVE'),
(r"\bcan't\b", "HAVEN'T YET"),
(r'\bimpossible\b', 'A CHALLENGE WORTH CONQUERING'),
],
"maxcringe": [
(r'\bgood\b', 'absolutely GAME-CHANGING and MIND-BLOWING'),
(r'\bwork\b', 'value-adding, paradigm-shifting hustle'),
(r'\bjob\b', 'purpose-driven mission'),
(r'\bteam\b', 'squad of absolute ROCKSTARS'),
(r'\bsuccess\b', 'WILD, UNPRECEDENTED success'),
(r'\blearned\b', 'had my MIND BLOWN learning'),
(r'\bexcited\b', 'BEYOND HUMBLED and literally SHAKING with excitement'),
(r'\bimportant\b', 'MISSION-CRITICAL and NON-NEGOTIABLE'),
(r'\bgreat\b', 'PHENOMENAL and WORLD-CLASS'),
],
}
replacements = all_replacements.get(style, all_replacements["maxcringe"])
for pattern, repl in replacements:
text = re.sub(pattern, repl, text, flags=re.IGNORECASE)
return text
def apply_template(text, style):
templates = {
"corporate": {
"prefixes": [
"Thrilled to announce that ",
"Excited to share that ",
"Pleased to report that ",
"After extensive cross-functional alignment, ",
"Following a comprehensive deep dive, ",
],
"suffixes": [
"\n\nThe synergies here are truly remarkable. Looking forward to circling back on next steps. 💼",
"\n\nThis represents a significant value-add for all stakeholders. Let's take this offline to discuss further. 📊",
"\n\nThe ROI on this initiative speaks for itself. Excited to operationalize at scale. 🎯",
"\n\nKey takeaway: when we align our strategic priorities and execute with velocity, the results speak for themselves. 💼",
],
},
"humblebrag": {
"prefixes": [
"Humbled and honored to share that ",
"I don't say this to brag, but ",
"Still processing the fact that ",
"Overwhelmed with gratitude that ",
"Pinch me moment: ",
"Blessed beyond measure to announce that ",
],
"suffixes": [
"\n\nI am not worthy of this blessing. Grateful beyond words. 🙏✨",
"\n\nThis wouldn't have been possible without my village. I'm just so humbled. 😌💫",
"\n\nFeeling so much gratitude. If I can achieve this, anyone can. 🙏",
"\n\nHumbled. Honored. Blessed. Grateful. Overwhelmed. 🙏✨💫",
],
},
"hustle": {
"prefixes": [
"While you were sleeping, ",
"While others were resting, ",
"No days off. ",
"The grind doesn't stop. ",
"4:30 AM. Coffee. Grind. Repeat. ",
"While they were making excuses, ",
],
"suffixes": [
"\n\n💤 Sleep is for the weak. 💪 Grind never stops. 🔥 No excuses.",
"\n\nThe grindset is real. While they sleep, we grind. While they complain, we build. 💪🔥",
"\n\nRemember: The only thing standing between you and your dreams is the snooze button. ⏰💪",
"\n\n🔥 Success isn't given. It's earned. On the grind. Every. Single. Day. 💪",
],
},
"thoughtleader": {
"prefixes": [
"Hot take: ",
"Unpopular opinion: ",
"Here's what nobody tells you: ",
"Let that sink in: ",
"I've been saying this for years: ",
"A thread 🧵: ",
],
"suffixes": [
"\n\nLet that sink in. 🧠\n\nAgree? Drop a 👍\nDisagree? Let's debate.",
"\n\nI said what I said. 🎤 The truth hurts, but it also sets you free. 🧠",
"\n\nMost won't understand this. The ones who do? They're already ahead. 🧠💡",
"\n\nSave this post. Come back to it in 6 months. You'll see. 🧠⏰",
],
},
"techbro": {
"prefixes": [
"🚀 Just shipped: ",
"⚡ Building in public update: ",
"💻 Late night coding session results: ",
"🔥 What I learned while building: ",
"🛠️ MVP update: ",
],
"suffixes": [
"\n\n🚀 Shipping > Talking. Building > Planning. Iterating > Perfecting. Let's go! ⚡",
"\n\nShip fast. Break things. Iterate. Scale. Repeat. 🚀⚡ #BuildInPublic",
"\n\nIf you're not embarrassed by your first version, you shipped too late. 🚀 Shipped. Learned. Iterating. ⚡",
],
},
"motivational": {
"prefixes": [
"🔥 REMINDER: ",
"⚡ WAKE UP CALL: ",
"💫 TRUTH BOMB: ",
"🌟 DON'T SCROLL PAST THIS: ",
"✨ READ THIS IF YOU'RE STRUGGLING: ",
"👑 DAILY AFFIRMATION: ",
],
"suffixes": [
"\n\n✨ Your only limit is you. Believe it. Achieve it. 🔥\n\nLike if you agree. Share if you care. Comment 'YES' if you're ready.",
"\n\n💪 You are stronger than you think. Braver than you believe. ✨\n\nTag someone who needs to hear this today.",
"\n\n🔥 The comeback is ALWAYS greater than the setback. Keep pushing. Keep going. ✨\n\nWho's with me? Drop a 🔥 below.",
"\n\n✨ Remember: Every expert was once a beginner. Your time is NOW. 💪\n\nShare this with someone who needs it.",
],
},
"maxcringe": {
"prefixes": [
"🚨 STOP SCROLLING! 🚨\n\nHumbled. Honored. Blessed. Thrilled. Overwhelmed. Grateful. Shaking. ",
"I literally can't even right now. ",
"THIS IS NOT A DRILL 🔥🔥🔥\n\nHumbled and honored to announce that ",
"Pinch me. Slap me. Call me crazy. ",
"🚀💥 GAME. CHANGER. 💥🚀\n\n",
],
"suffixes": [
"\n\n🙏 Humbled. Honored. Blessed. Grateful. Overwhelmed. Literally shaking.\n\n💥 Agree? Like!\n💬 Disagree? Comment!\n🔄 Care? Share!\n\n#Grateful #Blessed #Humbled #LinkedInLife #Motivation #Hustle #Grind #Success",
"\n\nThis is what they don't teach you in school. 🎓❌\n\n🔥🔥🔥\n\nWho else relates? Comment 'YES' below!\n\n#Leadership #Success #Motivation #Inspiration #Hustle #Grindset #Mindset #Growth #Goals #DreamBig",
"\n\nLet that sink in. 🧠 Now circle back and leverage this. 💼 Then go grind. 💪 And ship it. 🚀\n\nBEYOND HUMBLED. 🙏✨🔥💫\n\n#Synergy #Hustle #Grindset #Leadership #ThoughtLeadership #BuildInPublic #Grateful #Blessed #Mindset #10x #Scale #Disrupt",
"\n\nIf this post inspired even ONE person, it was worth it. 🥺🙏\n\n👉 Like if you're a GO-GETTER\n👉 Comment if you're UNSTOPPABLE\n👉 Share if you DARE TO DREAM\n\n#Motivation #Inspiration #Leadership #Blessed #Humble #Grind #Success #Winning #Champion #Mindset",
],
},
}
template = templates.get(style, templates["maxcringe"])
prefix = random.choice(template["prefixes"])
suffix = random.choice(template["suffixes"])
if text and text[0].islower():
text = text[0].upper() + text[1:]
return f"{prefix}{text}{suffix}"
def add_emojis(text, style):
emoji_sets = {
"corporate": ["📊", "📈", "🎯", "💼", "🏢", "💡", "📋", "🔑", "⚙️"],
"humblebrag": ["🙏", "✨", "💫", "😌", "🌟", "🥺", "💝", "🦋", "💎"],
"hustle": ["💪", "🔥", "⏰", "🏃", "💨", "🏋️", "🦁", "⚡", "🎯"],
"thoughtleader": ["🧠", "💡", "🤔", "👁️", "🎙️", "🔍", "📌", "⚖️"],
"techbro": ["🚀", "⚡", "💻", "🛠️", "🔧", "📱", "🤖", "⌨️", "🖥️"],
"motivational": ["🔥", "✨", "💪", "🌟", "💫", "👑", "🦁", "⭐", "🏆"],
"maxcringe": ["🔥", "🚀", "💪", "🙏", "✨", "💫", "🧠", "💼", "⚡", "📈", "🎯", "💎", "👑", "🌟", "👏"],
}
emojis = emoji_sets.get(style, emoji_sets["maxcringe"])
sentences = re.split(r'(?<=[.!?])\s+', text)
result = []
for i, sentence in enumerate(sentences):
result.append(sentence)
if i < len(sentences) - 1 and random.random() > 0.45:
result.append(f" {random.choice(emojis)}")
text = " ".join(result)
return text
def add_hashtags(text, style):
hashtags = {
"corporate": ["#Leadership", "#Strategy", "#Innovation", "#BusinessGrowth", "#Management", "#StakeholderValue"],
"humblebrag": ["#Blessed", "#Grateful", "#Humbled", "#Honored", "#Thankful", "#Overwhelmed"],
"hustle": ["#Hustle", "#Grind", "#NoDaysOff", "#5AMClub", "#Motivation", "#Grindset", "#EntrepreneurLife"],
"thoughtleader": ["#ThoughtLeadership", "#HotTake", "#UnpopularOpinion", "#ThinkDifferent", "#Contrarian", "#Insights"],
"techbro": ["#BuildInPublic", "#StartupLife", "#Shipping", "#TechStartup", "#MVP", "#Disrupt", "#Iteration"],
"motivational": ["#Motivation", "#Inspiration", "#BelieveInYourself", "#NeverGiveUp", "#DreamBig", "#YouGotThis"],
"maxcringe": ["#LinkedInLife", "#Grateful", "#Blessed", "#Humbled", "#Hustle", "#Leadership", "#Motivation", "#Inspiration", "#Growth", "#Mindset", "#Success", "#10x", "#Disrupt", "#Synergy", "#Grind"],
}
tags = hashtags.get(style, hashtags["maxcringe"])
num_tags = random.randint(3, min(6, len(tags)))
selected = random.sample(tags, num_tags)
return f"{text}\n\n{' '.join(selected)}"
def translate_with_rules(text, style_name):
style_info = STYLES.get(style_name, STYLES["Maximum Cringe 😬"])
style_key = style_info["key"]
text, was_spun = apply_positivity_spin(text)
result = apply_replacements(text, style_key)
result = apply_template(result, style_key)
result = add_emojis(result, style_key)
result = add_hashtags(result, style_key)
return result, was_spun
# ============================================================
# TRANSLATION ENGINE — AI-POWERED
# ============================================================
def translate_with_ai(text, style_name, api_key):
style_info = STYLES.get(style_name, STYLES["Maximum Cringe 😬"])
style_key = style_info["key"]
style_prompts = {
"corporate": (
"exaggerated corporate buzzword LinkedIn speak. Use synergy, leverage, paradigm shift, "
"circle back, deep dive, move the needle, operationalize, net-new, mission-critical, "
"value-add, stakeholders, cross-functional. IMPORTANT: Always spin anything negative "
"into a positive opportunity. Never say anything bad directly."
),
"humblebrag": (
"humble brag LinkedIn speak. Start with being humbled and honored, mention blessings "
"and gratitude while definitely bragging. Say things like 'I don't say this to brag but...' "
"then brag. IMPORTANT: Always spin anything negative into a blessing in disguise."
),
"hustle": (
"hustle culture LinkedIn speak. 5AM grind, no days off, outwork everyone, grindset, "
"beast mode. IMPORTANT: Always spin anything negative as fuel for the grind, a stepping "
"stone, or being tested by the universe."
),
"thoughtleader": (
"thought leader LinkedIn speak. Hot takes, unpopular opinions, 'let that sink in', "
"contrarian takes nobody asked for. IMPORTANT: Always spin negatives as profound lessons "
"or hidden truths that most people miss."
),
"techbro": (
"tech bro LinkedIn speak. Shipping, building in public, MVP, PMF, disrupting, iterating, "
"scaling. IMPORTANT: Always spin negatives as pivots, learning experiences, or 'iterating "
"on the product'. Never admit failure, only pivoting."
),
"motivational": (
"over-the-top motivational speaker LinkedIn speak. ALL CAPS REMINDERS, you are enough, "
"believe in yourself, never give up, inspirational but cringe. IMPORTANT: Always spin "
"negatives as SETBACKS that are really SETUPS for COMEBACKS."
),
"maxcringe": (
"maximum cringe LinkedIn speak. Combine ALL LinkedIn stereotypes: corporate buzzwords, "
"humble brags, hustle culture, excessive emojis, hashtag everything, call to actions like "
"'comment YES'. IMPORTANT: ALWAYS spin anything negative into something positive. If someone "
"lost their job, say they 'embraced a new chapter'. If they failed, say they 'discovered a "
"powerful lesson'. NEVER be negative. Everything is a blessing, opportunity, or stepping stone."
),
}
style_prompt = style_prompts.get(style_key, style_prompts["maxcringe"])
prompt = f"""<s>[INST] You are the LinkedIn Language Translator. Your #1 rule: NEVER say anything negative directly. Everything must be spun positive. Lost a job = "new chapter". Failed = "discovered a lesson". Fired = "freed to pursue bigger things".
Convert the following normal English into {style_prompt}. Only output the LinkedIn post, nothing else. No explanations. No quotes around the output.
Normal English: {text}[/INST]"""
models = [
"HuggingFaceH4/zephyr-7b-beta",
"mistralai/Mistral-7B-Instruct-v0.2",
"microsoft/phi-2",
]
last_error = None
for model in models:
try:
api_url = f"https://api-inference.huggingface.co/models/{model}"
headers = {"Authorization": f"Bearer {api_key}"}
payload = {
"inputs": prompt,
"parameters": {
"max_new_tokens": 400,
"temperature": 0.9,
"top_p": 0.95,
"do_sample": True,
"return_full_text": False,
},
}
response = requests.post(api_url, headers=headers, json=payload, timeout=60)
if response.status_code == 200:
result = response.json()
if isinstance(result, list) and len(result) > 0:
generated = result[0].get("generated_text", "").strip()
for tag in ["</s>", "<s>", "[/INST]", "[INST]"]:
generated = generated.replace(tag, "")
generated = generated.strip()
if len(generated) > 20:
return generated, None
elif response.status_code == 503:
try:
error_info = response.json()
wait_time = min(error_info.get("estimated_time", 20), 30)
except Exception:
wait_time = 15
time.sleep(wait_time)
response = requests.post(api_url, headers=headers, json=payload, timeout=60)
if response.status_code == 200:
result = response.json()
if isinstance(result, list) and len(result) > 0:
generated = result[0].get("generated_text", "").strip()
for tag in ["</s>", "<s>", "[/INST]", "[INST]"]:
generated = generated.replace(tag, "")
generated = generated.strip()
if len(generated) > 20:
return generated, None
else:
last_error = f"Model {model}: HTTP {response.status_code}"
elif response.status_code == 401:
return None, "Invalid API token. Check your HuggingFace token and try again."
elif response.status_code == 429:
last_error = f"Rate limited on {model}. Trying next..."
time.sleep(2)
continue
else:
last_error = f"Model {model}: HTTP {response.status_code}"
continue
except requests.exceptions.Timeout:
last_error = f"Model {model} timed out."
continue
except Exception as e:
last_error = f"Error with {model}: {str(e)[:80]}"
continue
return None, last_error or "All AI models unavailable. Using rule-based fallback."
# ============================================================
# UTILITY FUNCTIONS
# ============================================================
def calculate_cringe_score(text):
buzzwords = [
'synergy', 'leverage', 'paradigm', 'circle back', 'deep dive',
'bandwidth', 'humbled', 'honored', 'blessed', 'grateful',
'overwhelmed', 'hustle', 'grind', '5am', 'no days off',
'hot take', 'let that sink in', 'unpopular opinion',
'shipping', 'building in public', 'mvp', 'disrupt',
'reminder', 'game-changer', 'net-new', 'mission-critical',
'rockstar', 'thought leader', 'grindset', '10x', 'pmf',
'new chapter', 'opportunity', 'blessing in disguise',
'redirected', 'season of', 'growth opportunity',
]
text_lower = text.lower()
score = 0
for word in buzzwords:
score += text_lower.count(word) * 3
emoji_pattern = re.compile(
"[" "\U0001F600-\U0001F64F" "\U0001F300-\U0001F5FF" "\U0001F680-\U0001F6FF"
"\U0001F1E0-\U0001F1FF" "\U00002702-\U000027B0" "\U0001F900-\U0001F9FF"
"\U00002600-\U000026FF" "\U0000FE00-\U0000FE0F" "\U0001FA00-\U0001FA6F"
"\U0001FA70-\U0001FAFF" "\U000027BF" "]+", flags=re.UNICODE,
)
score += len(emoji_pattern.findall(text)) * 1
score += len(re.findall(r'#\w+', text)) * 2
score += len(re.findall(r'\b[A-Z]{3,}\b', text)) * 2
ctas = ['like if', 'share if', 'comment below', 'drop a', 'tag someone',
'agree?', 'who else', 'comment yes', 'read this', 'stop scrolling']
for cta in ctas:
if cta in text_lower:
score += 4
return min(100, score)
def get_cringe_level(score):
if score <= 20:
return "Mildly Corporate", "#4CAF50"
elif score <= 40:
return "Buzzword Alert", "#8BC34A"
elif score <= 60:
return "Pretty Cringe", "#FFC107"
elif score <= 80:
return "Maximum Cringe", "#FF5722"
else:
return "Nuclear Cringe ☢️", "#F44336"
# ============================================================
# MAIN APP
# ============================================================
def main():
inject_css(st.session_state.theme)
# ---- SIDEBAR ----
with st.sidebar:
st.markdown("## ⚙️ Settings")
# Theme toggle
st.markdown("#### 🎨 Theme")
col_t1, col_t2 = st.columns(2)
with col_t1:
if st.button("🌙 Dark", use_container_width=True,
type="primary" if st.session_state.theme == "dark" else "secondary"):
st.session_state.theme = "dark"
st.rerun()
with col_t2:
if st.button("☀️ Light", use_container_width=True,
type="primary" if st.session_state.theme == "light" else "secondary"):
st.session_state.theme = "light"
st.rerun()
st.markdown("<hr class='divider-line'>", unsafe_allow_html=True)
# Style selector
st.markdown("#### 🎭 Translation Style")
style_names = list(STYLES.keys())
selected_style = st.selectbox(
"Choose your LinkedIn persona:",
style_names, index=0, label_visibility="collapsed",
)
style_info = STYLES[selected_style]
fg = "#c9d1d9" if st.session_state.theme == "dark" else "#24292f"
st.markdown(
f"<div style='padding:8px 12px;border-radius:8px;background:{style_info['color']}22;"
f"border-left:3px solid {style_info['color']};font-size:0.82rem;color:{fg}'>"
f"{style_info['desc']}</div>",
unsafe_allow_html=True,
)
st.markdown("<hr class='divider-line'>", unsafe_allow_html=True)
# AI Mode
st.markdown("#### 🤖 AI Mode")
use_ai = st.toggle("Enable AI Translation", value=False)
if use_ai:
st.caption("Uses HuggingFace Inference API for smarter, more creative translations.")
# Using key="hf_api_key" persists the token in session state automatically
st.text_input(
"HF API Token", type="password", placeholder="hf_xxxxxxxxxxxxx",
help="Get a free token at huggingface.co/settings/tokens",
key="hf_api_key"
)
# Read from session state so it persists across reruns
hf_api_key = st.session_state.get("hf_api_key", "")
if not hf_api_key:
st.warning("⚠️ Enter your free HF token for AI mode. Falls back to rules if empty.")
else:
hf_api_key = ""
st.markdown("<hr class='divider-line'>", unsafe_allow_html=True)
# Stats
st.markdown("#### 📊 Stats")
col_s1, col_s2 = st.columns(2)
with col_s1:
st.markdown(
f"<div class='stat-box'><div class='stat-value'>{st.session_state.translation_count}</div>"
f"<div class='stat-label'>Translations</div></div>", unsafe_allow_html=True,
)
with col_s2:
st.markdown(
f"<div class='stat-box'><div class='stat-value'>{len(st.session_state.history)}</div>"
f"<div class='stat-label'>In History</div></div>", unsafe_allow_html=True,
)
st.markdown("<hr class='divider-line'>", unsafe_allow_html=True)
# History
st.markdown("#### 📜 History")
if st.session_state.history:
if st.button("🗑️ Clear History", use_container_width=True):
st.session_state.history = []
st.rerun()
for item in reversed(st.session_state.history[-10:]):
truncated_input = item["input"][:50] + ("..." if len(item["input"]) > 50 else "")
style_color = STYLES.get(item["style"], {}).get("color", "#0A66C2")
source_icon = "🤖" if item.get("ai") else "⚙️"
st.markdown(
f"<div class='history-item'>"
f"<span class='style-badge' style='background:{style_color}'>{item['style'].split(' ')[0]}</span> "
f"<span style='font-size:0.75rem'>{source_icon}</span><br>"
f"<b>Input:</b> {html_lib.escape(truncated_input)}<br>"
f"<b>Time:</b> {item['time']} • <b>Cringe:</b> {item['cringe']}/100"
f"</div>", unsafe_allow_html=True,
)
else:
st.caption("No translations yet. Start translating!")
# ---- MAIN CONTENT ----
# Header
st.markdown('<h1 class="main-title">LinkedIn Translator</h1>', unsafe_allow_html=True)
st.markdown('<p class="subtitle">Transform normal English into the language of LinkedIn 🤝</p>', unsafe_allow_html=True)
# Example chips
st.markdown("##### 💡 Try an example:")
chip_cols = st.columns(4)
for i, example in enumerate(EXAMPLE_INPUTS[:8]):
col_idx = i % 4
with chip_cols[col_idx]:
if st.button(example, key=f"ex_{i}", use_container_width=True):
st.session_state.input_area = example
st.rerun()
st.markdown("<br>", unsafe_allow_html=True)
# Input / Output columns
col_input, col_output = st.columns(2)
with col_input:
st.markdown(
"<div class='input-card'><div class='card-label input-label'>📝 Normal English</div>",
unsafe_allow_html=True,
)
input_text = st.text_area(
"Enter your text:",
height=200,
label_visibility="collapsed",
placeholder="Type something normal here...\n\ne.g., I lost my job",
key="input_area",
)
char_count = len(input_text) if input_text else 0
word_count = len(input_text.split()) if input_text and input_text.strip() else 0
st.markdown(
f"<div style='font-size:0.75rem;color:#8b949e;text-align:right;'>"
f"{word_count} words • {char_count}/2000 characters</div>",
unsafe_allow_html=True,
)
st.markdown("</div>", unsafe_allow_html=True)
with col_output:
st.markdown(
"<div class='output-card'><div class='card-label output-label'>💼 LinkedIn Language</div>",
unsafe_allow_html=True,
)
if st.session_state.last_output:
safe_output = html_lib.escape(st.session_state.last_output).replace("\n", "<br>")
# Build tags row
tags = []
if st.session_state.get("last_ai_used"):
tags.append('<span class="ai-tag">🤖 AI Generated</span>')
else:
tags.append('<span class="rule-tag">⚙️ Rule-Based</span>')
if st.session_state.get("was_spun"):
tags.append('<span class="spin-tag">✨ Positivity Spin</span>')
tags_html = f"<div class='tags-container'>{'' .join(tags)}</div>"
st.markdown(
f"{tags_html}<div class='output-display'>{safe_output}</div>",
unsafe_allow_html=True,
)
else:
st.markdown(
"<div class='output-empty'>Your LinkedIn masterpiece will appear here ✨</div>",
unsafe_allow_html=True,
)
st.markdown("</div>", unsafe_allow_html=True)
# Action buttons
col_b1, col_b2, col_b3, col_b4 = st.columns([2, 1, 1, 1])
with col_b1:
translate_btn = st.button("🔄 **Translate to LinkedIn**", use_container_width=True, type="primary")
with col_b2:
copy_btn = st.button("📋 Copy", use_container_width=True)
with col_b3:
clear_btn = st.button("🗑️ Clear", use_container_width=True)
with col_b4:
download_btn = st.download_button(
"💾 Save",
data=st.session_state.last_output or "",
file_name="linkedin_post.txt",
mime="text/plain",
use_container_width=True,
disabled=not st.session_state.last_output,
)
# ---- BUTTON ACTIONS ----
if translate_btn:
if not input_text or not input_text.strip():
st.toast("⚠️ Please enter some text to translate!", icon="⚠️")
elif len(input_text) > 2000:
st.toast("⚠️ Text too long! Max 2000 characters.", icon="⚠️")
else:
with st.spinner("🔄 Translating to LinkedIn-speak..."):
output_text = ""
was_spun = False
ai_used = False
ai_error = None
if use_ai and hf_api_key:
output_text, ai_error = translate_with_ai(input_text, selected_style, hf_api_key)
if output_text:
ai_used = True
st.toast("✅ AI translation complete!", icon="🤖")
else:
output_text, was_spun = translate_with_rules(input_text, selected_style)
if ai_error:
st.toast(f"⚠️ AI unavailable — {ai_error}", icon="⚡")
else:
st.toast("⚠️ AI unavailable — used rule-based fallback.", icon="⚡")
else:
output_text, was_spun = translate_with_rules(input_text, selected_style)
time.sleep(0.3)
st.toast("✅ Translation complete!", icon="💼")
# Store results
st.session_state.last_output = output_text
st.session_state.last_input = input_text
st.session_state.last_style = selected_style
st.session_state.was_spun = was_spun
st.session_state.last_ai_used = ai_used
st.session_state.translation_count += 1
cringe = calculate_cringe_score(output_text)
st.session_state.history.append({
"input": input_text,
"output": output_text,
"style": selected_style,
"cringe": cringe,
"time": datetime.now().strftime("%H:%M"),
"ai": ai_used,
"spun": was_spun,
})
st.rerun()
if copy_btn:
if st.session_state.last_output:
safe_text = st.session_state.last_output.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$").replace("\n", "\\n")
components.html(
f"""
<script>
(function() {{
const text = `{safe_text}`.replace(/\\\\n/g, '\\n');
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {{
document.execCommand('copy');
}} catch(e) {{
navigator.clipboard.writeText(text).catch(function(){{}});
}}
document.body.removeChild(textarea);
}})();
</script>
""",
height=0,
)
st.toast("📋 Copied to clipboard!", icon="✅")
else:
st.toast("⚠️ Nothing to copy!", icon="⚠️")
if clear_btn:
st.session_state.last_output = ""
st.session_state.last_input = ""
st.session_state.was_spun = False
st.session_state.last_ai_used = False
st.session_state.input_area = ""
st.rerun()
# ---- CRINGE METER ----
if st.session_state.last_output:
st.markdown("<br>", unsafe_allow_html=True)
cringe_score = calculate_cringe_score(st.session_state.last_output)
cringe_level, cringe_color = get_cringe_level(cringe_score)
st.markdown("##### 📊 Cringe Meter")
col_cm1, col_cm2 = st.columns([3, 1])
with col_cm1:
st.markdown(
f"<div class='cringe-bar-bg'>"
f"<div class='cringe-bar-fill' style='width:{cringe_score}%;background:{cringe_color};'></div>"
f"</div>",
unsafe_allow_html=True,
)
with col_cm2:
st.markdown(
f"<div style='text-align:center;font-size:1.1rem;font-weight:800;color:{cringe_color};'>"
f"{cringe_score}/100 — {cringe_level}</div>",
unsafe_allow_html=True,
)
# Show spin notice
if st.session_state.get("was_spun"):
st.info("✨ **Positivity Spin Applied** — Negative phrases were automatically rephrased because on LinkedIn, everything is an opportunity! 🌈")
# ---- FOOTER ----
st.markdown("<br><br>", unsafe_allow_html=True)
footer_color = "#484f58" if st.session_state.theme == "dark" else "#8b949e"
st.markdown(
f"<div style='text-align:center;font-size:0.78rem;color:{footer_color};'>"
f"Built with ❤️ and excessive buzzwords • LinkedIn Translator v2.1<br>"
f"Rule-based translation always works • Add HF API token for AI-powered translations<br>"
f"✨ Positivity Spin: Because on LinkedIn, nothing is ever truly negative"
f"</div>", unsafe_allow_html=True,
)
if __name__ == "__main__":
main()