Files
oca-py.org/index.html
T
2026-09-13 10:50:49 -03:00

1227 lines
69 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OCA Paraguay — Comunidad Odoo de Paraguay</title>
<meta name="description" content="OCA Paraguay: la comunidad hispanohablante de la Odoo Community Association en Paraguay. Noticias, tips, eventos y una red de personas apasionadas por el ERP de código abierto." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Sora:wght@600;700;800&display=swap" rel="stylesheet" />
<style>
:root {
--plum: #714B67;
--plum-dark: #4b2f43;
--violet: #6C4CE0;
--violet-soft: #8f7bf0;
--mint: #16C79A;
--mint-dark: #0e8f6e;
--py-red: #D52B1E;
--py-blue: #0038A8;
--ink: #1b1420;
--muted: #6a6172;
--bg: #fbfaff;
--card: #ffffff;
--line: #ece8f4;
--shadow-sm: 0 2px 8px rgba(45, 30, 60, .06);
--shadow: 0 14px 40px rgba(45, 30, 60, .10);
--shadow-lg: 0 30px 70px rgba(45, 30, 60, .16);
--radius: 20px;
--max: 1160px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.65;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, .brand { font-family: 'Sora', 'Inter', sans-serif; letter-spacing: -0.02em; line-height: 1.12; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.container { width: min(var(--max), 92vw); margin: 0 auto; }
/* ---------- Background decorative ---------- */
.bg-blobs { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.blob {
position: absolute; border-radius: 50%; filter: blur(80px); opacity: .5;
animation: float 18s ease-in-out infinite;
}
.blob.b1 { width: 460px; height: 460px; background: #d9cdfb; top: -120px; left: -100px; }
.blob.b2 { width: 400px; height: 400px; background: #c6f3e6; top: 40%; right: -140px; animation-delay: -6s; }
.blob.b3 { width: 360px; height: 360px; background: #ffd9d4; bottom: -120px; left: 30%; animation-delay: -11s; }
@keyframes float {
0%, 100% { transform: translate3d(0,0,0) scale(1); }
33% { transform: translate3d(30px,-40px,0) scale(1.08); }
66% { transform: translate3d(-25px,25px,0) scale(.95); }
}
/* ---------- Nav ---------- */
header.nav {
position: sticky; top: 0; z-index: 100;
backdrop-filter: blur(14px);
background: rgba(251, 250, 255, .78);
border-bottom: 1px solid transparent;
transition: border-color .3s, box-shadow .3s, background .3s;
}
header.nav.scrolled { border-color: var(--line); box-shadow: var(--shadow-sm); background: rgba(251,250,255,.92); }
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 72px; }
.brand { display: flex; align-items: center; gap: 12px; font-weight: 800; font-size: 1.12rem; }
.brand .logo {
width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
background: linear-gradient(135deg, var(--violet), var(--plum));
color: #fff; font-weight: 800; font-size: .95rem; letter-spacing: .5px;
box-shadow: 0 8px 20px rgba(108, 76, 224, .35);
position: relative; overflow: hidden;
}
.brand .logo::after {
content: ""; position: absolute; inset: 0;
background: linear-gradient(120deg, transparent 30%, rgba(255,255,255,.5) 50%, transparent 70%);
transform: translateX(-120%);
animation: shine 4.5s ease-in-out infinite;
}
.brand .logo-img { background: none; box-shadow: none; overflow: visible; }
.brand .logo-img::after { display: none; }
.brand .logo-img img { width: 42px; height: 42px; object-fit: contain; }
@media (max-width: 720px) { .brand .logo-img img { width: 36px; height: 36px; } }
@keyframes shine { 0%, 60% { transform: translateX(-120%); } 80%, 100% { transform: translateX(120%); } }
.brand small { display: block; font-family: 'Inter'; font-weight: 500; font-size: .68rem; color: var(--muted); letter-spacing: .02em; }
nav ul { display: flex; gap: 6px; list-style: none; align-items: center; }
nav a.link {
padding: 8px 14px; border-radius: 999px; font-size: .92rem; font-weight: 500; color: var(--muted);
transition: color .2s, background .2s;
}
nav a.link:hover { color: var(--violet); background: rgba(108,76,224,.08); }
.btn {
display: inline-flex; align-items: center; gap: 8px; cursor: pointer; border: none;
padding: 11px 20px; border-radius: 999px; font-weight: 600; font-size: .92rem;
transition: transform .18s ease, box-shadow .25s, background .25s;
font-family: inherit;
}
.btn-primary { background: linear-gradient(135deg, var(--violet), var(--plum)); color: #fff; box-shadow: 0 10px 24px rgba(108,76,224,.32); }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 16px 32px rgba(108,76,224,.42); }
.btn-ghost { background: #fff; color: var(--ink); border: 1px solid var(--line); box-shadow: var(--shadow-sm); }
.btn-ghost:hover { transform: translateY(-2px); border-color: var(--violet-soft); color: var(--violet); }
.burger { display: none; background: none; border: none; cursor: pointer; padding: 8px; }
.burger span { display: block; width: 22px; height: 2px; background: var(--ink); margin: 5px 0; border-radius: 2px; transition: .3s; }
/* ---------- Hero ---------- */
.hero { padding: 78px 0 40px; position: relative; }
.hero-grid { display: grid; grid-template-columns: 1.08fr .92fr; gap: 54px; align-items: center; }
.pill {
display: inline-flex; align-items: center; gap: 9px; padding: 7px 15px; border-radius: 999px;
background: #fff; border: 1px solid var(--line); box-shadow: var(--shadow-sm);
font-size: .8rem; font-weight: 600; color: var(--plum); margin-bottom: 22px;
}
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--mint); position: relative; }
.dot::after { content:""; position:absolute; inset:0; border-radius:50%; background: var(--mint); animation: pulse 1.8s ease-out infinite; }
@keyframes pulse { 0% { transform: scale(1); opacity: .8; } 100% { transform: scale(3); opacity: 0; } }
.hero h1 { font-size: clamp(2.3rem, 5vw, 3.7rem); font-weight: 800; margin-bottom: 20px; }
.grad-text {
background: linear-gradient(120deg, var(--violet) 10%, var(--plum) 50%, var(--mint-dark) 95%);
background-size: 220% 220%;
-webkit-background-clip: text; background-clip: text; color: transparent;
animation: gradShift 7s ease infinite;
}
@keyframes gradShift { 0%,100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
.hero p.lead { font-size: 1.12rem; color: var(--muted); max-width: 540px; margin-bottom: 30px; }
.hero-cta { display: flex; gap: 14px; flex-wrap: wrap; }
.hero-meta { display: flex; gap: 26px; margin-top: 38px; flex-wrap: wrap; }
.hero-meta div strong { display: block; font-family: 'Sora'; font-size: 1.6rem; font-weight: 800; color: var(--plum); }
.hero-meta div span { font-size: .82rem; color: var(--muted); }
/* Hero visual stack */
.hero-visual { position: relative; height: 420px; }
.card-float {
position: absolute; background: #fff; border: 1px solid var(--line); border-radius: 18px;
box-shadow: var(--shadow); padding: 18px; width: 240px;
}
.cf-1 { top: 8%; left: 0; animation: bob 6s ease-in-out infinite; }
.cf-2 { top: 36%; right: 0; animation: bob 6s ease-in-out infinite -2s; width: 220px; }
.cf-3 { bottom: 4%; left: 12%; animation: bob 6s ease-in-out infinite -4s; width: 210px; }
@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
.card-float h4 { font-size: .95rem; margin-bottom: 5px; }
.card-float p { font-size: .78rem; color: var(--muted); }
.cf-ico { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; margin-bottom: 11px; color: #fff; }
.ico-violet { background: linear-gradient(135deg, var(--violet), var(--violet-soft)); }
.ico-mint { background: linear-gradient(135deg, var(--mint), var(--mint-dark)); }
.ico-plum { background: linear-gradient(135deg, var(--plum), var(--plum-dark)); }
.progress { height: 6px; border-radius: 6px; background: #efeaf7; margin-top: 12px; overflow: hidden; }
.progress i { display: block; height: 100%; width: 40%; border-radius: 6px; background: linear-gradient(90deg, var(--violet), var(--mint)); animation: grow 3s ease-in-out infinite; }
@keyframes grow { 0%,100% { width: 35%; } 50% { width: 82%; } }
/* ---------- Sections ---------- */
section { padding: 74px 0; }
.sec-head { max-width: 660px; margin-bottom: 46px; }
.sec-head .kicker {
font-size: .8rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--violet);
display: inline-flex; align-items: center; gap: 8px; margin-bottom: 12px;
}
.sec-head .kicker::before { content: ""; width: 26px; height: 2px; background: var(--violet); border-radius: 2px; }
.sec-head h2 { font-size: clamp(1.8rem, 3.6vw, 2.6rem); font-weight: 800; margin-bottom: 14px; }
.sec-head p { color: var(--muted); font-size: 1.02rem; }
.sec-head.center { margin-left: auto; margin-right: auto; text-align: center; }
.sec-head.center .kicker { justify-content: center; }
/* Reveal animation */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity .7s cubic-bezier(.2,.7,.2,1), transform .7s cubic-bezier(.2,.7,.2,1); }
.reveal.in { opacity: 1; transform: none; }
/* ---------- News ---------- */
.news-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; }
.news-card {
background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
box-shadow: var(--shadow-sm); display: flex; flex-direction: column;
transition: transform .3s cubic-bezier(.2,.7,.2,1), box-shadow .3s, border-color .3s;
}
.news-card:hover { transform: translateY(-8px); box-shadow: var(--shadow-lg); border-color: transparent; }
.news-top { height: 158px; position: relative; overflow: hidden; display: grid; place-items: center; color: #fff; }
.news-top .tag {
position: absolute; top: 14px; left: 14px; background: rgba(255,255,255,.22); backdrop-filter: blur(6px);
padding: 5px 12px; border-radius: 999px; font-size: .72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
border: 1px solid rgba(255,255,255,.35);
}
.news-top svg { width: 54px; height: 54px; opacity: .95; transition: transform .5s; }
.news-card:hover .news-top svg { transform: scale(1.14) rotate(-4deg); }
.nt-1 { background: linear-gradient(135deg, var(--violet), var(--plum)); }
.nt-2 { background: linear-gradient(135deg, var(--mint), #0b7d8a); }
.nt-3 { background: linear-gradient(135deg, #ff8a5c, var(--py-red)); }
.nt-4 { background: linear-gradient(135deg, #3d7de0, var(--py-blue)); }
.news-body { padding: 22px 22px 24px; display: flex; flex-direction: column; flex: 1; }
.news-date { font-size: .76rem; color: var(--muted); font-weight: 600; margin-bottom: 9px; }
.news-body h3 { font-size: 1.08rem; margin-bottom: 10px; font-weight: 700; }
.news-body p { font-size: .88rem; color: var(--muted); flex: 1; }
.news-body a.more { margin-top: 16px; font-weight: 600; font-size: .86rem; color: var(--violet); display: inline-flex; align-items: center; gap: 6px; }
.news-body a.more svg { width: 15px; height: 15px; transition: transform .25s; }
.news-card:hover a.more svg { transform: translateX(4px); }
/* ---------- Tips ---------- */
.tips-wrap { display: grid; grid-template-columns: .9fr 1.1fr; gap: 44px; align-items: start; }
.tip-list { display: flex; flex-direction: column; gap: 14px; }
.tip {
background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 18px 20px;
box-shadow: var(--shadow-sm); cursor: pointer; transition: border-color .25s, box-shadow .25s, transform .2s;
}
.tip:hover { border-color: var(--violet-soft); transform: translateX(4px); }
.tip.open { border-color: var(--violet); box-shadow: var(--shadow); }
.tip-head { display: flex; align-items: center; gap: 14px; }
.tip-num {
width: 34px; height: 34px; flex: 0 0 34px; border-radius: 10px; display: grid; place-items: center;
background: linear-gradient(135deg, var(--violet), var(--plum)); color: #fff; font-weight: 700; font-size: .85rem;
font-family: 'Sora';
}
.tip-head h4 { font-size: 1rem; flex: 1; font-weight: 600; }
.tip-head .chev { width: 18px; height: 18px; color: var(--muted); transition: transform .3s; flex: 0 0 18px; }
.tip.open .chev { transform: rotate(180deg); color: var(--violet); }
.tip-body { max-height: 0; overflow: hidden; transition: max-height .4s cubic-bezier(.2,.7,.2,1), margin .4s; }
.tip.open .tip-body { max-height: 260px; margin-top: 12px; }
.tip-body p { font-size: .88rem; color: var(--muted); }
.tip-body code {
background: #f3effa; color: var(--plum); padding: 2px 7px; border-radius: 6px; font-size: .82rem;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
}
.tip-side {
background: linear-gradient(160deg, var(--plum-dark), var(--violet)); border-radius: 22px; padding: 34px;
color: #fff; position: relative; overflow: hidden; box-shadow: var(--shadow);
}
.tip-side::before {
content: ""; position: absolute; width: 220px; height: 220px; border-radius: 50%;
background: rgba(255,255,255,.10); top: -70px; right: -50px;
}
.tip-side h3 { font-size: 1.5rem; margin-bottom: 14px; position: relative; }
.tip-side p { font-size: .94rem; color: rgba(255,255,255,.82); margin-bottom: 22px; position: relative; }
.tip-side ul { list-style: none; position: relative; display: flex; flex-direction: column; gap: 12px; }
.tip-side li { display: flex; gap: 11px; align-items: flex-start; font-size: .9rem; }
.tip-side li svg { width: 20px; height: 20px; flex: 0 0 20px; color: var(--mint); margin-top: 2px; }
/* ---------- Events ---------- */
.events { background: linear-gradient(180deg, transparent, #f4f0fb 40%, transparent); }
.event-list { display: flex; flex-direction: column; gap: 16px; }
.event {
display: grid; grid-template-columns: 92px 1fr auto; gap: 24px; align-items: center;
background: var(--card); border: 1px solid var(--line); border-radius: 18px; padding: 20px 26px;
box-shadow: var(--shadow-sm); transition: transform .28s, box-shadow .28s, border-color .28s;
}
.event:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: var(--violet-soft); }
.event-date {
text-align: center; border-right: 1px solid var(--line); padding-right: 22px;
}
.event-date .d { font-family: 'Sora'; font-size: 1.9rem; font-weight: 800; color: var(--plum); line-height: 1; }
.event-date .m { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--violet); }
.event-info h3 { font-size: 1.12rem; margin-bottom: 5px; }
.event-info p { font-size: .88rem; color: var(--muted); }
.event-info .loc { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; font-size: .78rem; font-weight: 600; color: var(--plum); }
.event-info .loc svg { width: 14px; height: 14px; }
.badge {
font-size: .72rem; font-weight: 700; padding: 5px 12px; border-radius: 999px; text-transform: uppercase; letter-spacing: .05em;
background: rgba(108,76,224,.1); color: var(--violet);
}
.badge.presencial { background: rgba(213,43,30,.1); color: var(--py-red); }
.badge.taller { background: rgba(22,199,154,.14); color: var(--mint-dark); }
/* ---------- CTA / Join ---------- */
.join-box {
background: linear-gradient(140deg, var(--plum-dark) 0%, var(--violet) 62%, #7a5be8 100%);
border-radius: 28px; padding: 54px; color: #fff; position: relative; overflow: hidden; box-shadow: var(--shadow-lg);
}
.join-box::after {
content: ""; position: absolute; inset: 0;
background: radial-gradient(circle at 80% 10%, rgba(22,199,154,.35), transparent 45%);
}
.join-grid { display: grid; grid-template-columns: 1.3fr .7fr; gap: 40px; align-items: center; position: relative; z-index: 1; }
.join-grid h2 { font-size: clamp(1.7rem, 3.4vw, 2.4rem); margin-bottom: 14px; }
.join-grid p { color: rgba(255,255,255,.85); max-width: 520px; margin-bottom: 26px; }
.join-actions { display: flex; gap: 14px; flex-wrap: wrap; }
.btn-white { background: #fff; color: var(--plum); }
.btn-white:hover { transform: translateY(-2px); box-shadow: 0 14px 30px rgba(0,0,0,.25); }
.btn-outline { background: rgba(255,255,255,.1); color: #fff; border: 1px solid rgba(255,255,255,.4); }
.btn-outline:hover { background: rgba(255,255,255,.2); transform: translateY(-2px); }
.join-stats { display: flex; flex-direction: column; gap: 18px; }
.join-stat { background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.16); border-radius: 16px; padding: 18px 22px; backdrop-filter: blur(6px); }
.join-stat strong { font-family: 'Sora'; font-size: 1.7rem; display: block; }
.join-stat span { font-size: .82rem; color: rgba(255,255,255,.8); }
/* ---------- Footer ---------- */
footer { border-top: 1px solid var(--line); padding: 54px 0 30px; margin-top: 40px; background: #fff; }
.foot-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 34px; margin-bottom: 40px; }
.foot-brand p { color: var(--muted); font-size: .88rem; margin-top: 16px; max-width: 300px; }
footer h5 { font-family: 'Sora'; font-size: .9rem; margin-bottom: 16px; }
footer ul { list-style: none; display: flex; flex-direction: column; gap: 10px; }
footer ul a { font-size: .88rem; color: var(--muted); transition: color .2s, padding-left .2s; }
footer ul a:hover { color: var(--violet); padding-left: 4px; }
.foot-bottom { border-top: 1px solid var(--line); padding-top: 24px; display: flex; justify-content: space-between; gap: 18px; flex-wrap: wrap; font-size: .82rem; color: var(--muted); }
.socials { display: flex; gap: 10px; margin-top: 18px; }
.socials a {
width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
border: 1px solid var(--line); color: var(--muted); transition: .25s;
}
.socials a:hover { background: var(--violet); color: #fff; border-color: transparent; transform: translateY(-3px); }
.socials svg { width: 18px; height: 18px; }
.flag-strip { height: 4px; width: 100%; background: linear-gradient(90deg, var(--py-red) 33.3%, #fff 33.3% 66.6%, var(--py-blue) 66.6%); border-radius: 4px; }
/* ---------- Estado Odoo CE ---------- */
.state-grid { display: grid; grid-template-columns: 1fr 1.12fr; gap: 40px; align-items: start; }
.state-facts { display: flex; flex-direction: column; gap: 14px; }
.fact {
display: flex; gap: 14px; align-items: flex-start; background: var(--card);
border: 1px solid var(--line); border-radius: 16px; padding: 16px 18px; box-shadow: var(--shadow-sm);
transition: transform .25s, border-color .25s, box-shadow .25s;
}
.fact:hover { transform: translateY(-3px); border-color: var(--violet-soft); box-shadow: var(--shadow); }
.fact .fi { width: 38px; height: 38px; flex: 0 0 38px; border-radius: 11px; display: grid; place-items: center; color: #fff; }
.fact h4 { font-size: .98rem; margin-bottom: 3px; }
.fact p { font-size: .84rem; color: var(--muted); }
.fact code { background: #f3effa; color: var(--plum); padding: 1px 6px; border-radius: 6px; font-size: .8rem; font-family: ui-monospace, 'SF Mono', Menlo, monospace; }
.mod-panel { background: var(--card); border: 1px solid var(--line); border-radius: 20px; box-shadow: var(--shadow); overflow: hidden; }
.mod-head {
padding: 20px 24px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
border-bottom: 1px solid var(--line); background: linear-gradient(135deg, var(--plum-dark), var(--violet)); color: #fff;
}
.mod-head h3 { font-size: 1.08rem; }
.branch-pill {
display: inline-flex; align-items: center; gap: 7px; font-size: .76rem; font-weight: 700;
background: rgba(255,255,255,.18); border: 1px solid rgba(255,255,255,.3); padding: 5px 12px; border-radius: 999px;
}
.branch-pill .dot { background: var(--mint); }
.mod-list { list-style: none; }
.mod-list li { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 15px 24px; border-bottom: 1px solid var(--line); align-items: center; transition: background .2s; }
.mod-list li:last-child { border-bottom: none; }
.mod-list li:hover { background: #faf8ff; }
.mod-list .name { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: .86rem; font-weight: 600; color: var(--plum); }
.mod-list .desc { font-size: .8rem; color: var(--muted); }
.ver { font-size: .72rem; font-weight: 700; color: var(--violet); background: rgba(108,76,224,.1); padding: 4px 10px; border-radius: 999px; white-space: nowrap; }
.mod-foot { padding: 16px 24px; font-size: .78rem; color: var(--muted); border-top: 1px solid var(--line); background: #faf8ff; }
.mod-foot a { color: var(--violet); font-weight: 600; }
.mod-foot a:hover { text-decoration: underline; }
/* ---------- Hackathon ---------- */
.hack-box {
position: relative; border-radius: 30px; padding: 56px; color: #fff; overflow: hidden;
background: linear-gradient(135deg, #1b1030 0%, #3a1d63 45%, #6C4CE0 100%);
box-shadow: var(--shadow-lg);
}
.hack-box::before {
content: ""; position: absolute; inset: -40%;
background: conic-gradient(from 0deg, rgba(108,76,224,0), rgba(22,199,154,.4), rgba(213,43,30,.28), rgba(108,76,224,0));
filter: blur(70px); opacity: .75; animation: spin 16s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.hack-inner { position: relative; z-index: 1; }
.hack-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: 44px; align-items: start; }
.hack-badge {
display: inline-flex; align-items: center; gap: 9px; padding: 7px 15px; border-radius: 999px;
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.28);
font-size: .78rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 20px;
}
.hack-badge .dot { background: var(--mint); }
.hack-copy h2 { font-size: clamp(1.8rem, 3.6vw, 2.7rem); margin-bottom: 14px; }
.hack-copy h2 .grad-text { background: linear-gradient(120deg, #fff 10%, #c6f3e6 50%, #d9cdfb 95%); background-size: 220% 220%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: gradShift 7s ease infinite; }
.hack-copy > p { color: rgba(255,255,255,.85); font-size: 1.02rem; max-width: 520px; }
.hack-dates { display: inline-flex; align-items: center; gap: 10px; margin: 90px 0; font-weight: 700; font-size: 1.235rem; }
.hack-dates svg { width: 18px; height: 18px; color: var(--mint); }
.countdown { display: flex; gap: 12px; margin: 0 0 30px; flex-wrap: wrap; }
.cd-unit {
background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.18); border-radius: 14px;
padding: 12px 16px; text-align: center; min-width: 74px; backdrop-filter: blur(6px);
}
.cd-unit strong { font-family: 'Sora'; font-size: 1.7rem; display: block; line-height: 1; }
.cd-unit span { font-size: .66rem; text-transform: uppercase; letter-spacing: .1em; color: rgba(255,255,255,.75); }
.hack-goals { margin-top: 42px; }
.hack-goals > h3 { font-size: 1.3rem; margin-bottom: 18px; display: flex; align-items: center; gap: 12px; }
.hack-goals > h3::before { content: ""; width: 28px; height: 2px; background: var(--mint); border-radius: 2px; }
.hack-targets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.hack-target {
display: flex; gap: 12px; align-items: flex-start; background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.14); border-radius: 14px; padding: 18px;
transition: transform .28s cubic-bezier(.2,.7,.2,1), background .28s, box-shadow .28s;
}
.hack-target:hover { transform: translateY(-6px); background: rgba(255,255,255,.15); box-shadow: 0 18px 36px rgba(0,0,0,.25); }
.hack-target svg { width: 22px; height: 22px; flex: 0 0 22px; color: var(--mint); margin-top: 2px; }
.hack-target h4 { font-size: 1rem; margin-bottom: 5px; }
.hack-target p { font-size: .84rem; color: rgba(255,255,255,.78); }
.hack-form { background: #fff; color: var(--ink); border-radius: 22px; padding: 30px; box-shadow: 0 30px 60px rgba(0,0,0,.32); }
.hack-form h3 { font-size: 1.25rem; margin-bottom: 6px; }
.hack-form > p { font-size: .86rem; color: var(--muted); margin-bottom: 20px; }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: .8rem; font-weight: 600; margin-bottom: 6px; }
.field input, .field textarea {
width: 100%; padding: 12px 14px; border: 1px solid var(--line); border-radius: 12px;
font-family: inherit; font-size: .9rem; background: #fbfaff; color: var(--ink);
transition: border-color .2s, box-shadow .2s, background .2s;
}
.field input:focus, .field textarea:focus { outline: none; border-color: var(--violet); box-shadow: 0 0 0 4px rgba(108,76,224,.14); background: #fff; }
.field textarea { resize: vertical; min-height: 84px; }
.field .err { color: var(--py-red); font-size: .74rem; margin-top: 5px; display: none; }
.field.invalid input, .field.invalid textarea { border-color: var(--py-red); box-shadow: 0 0 0 4px rgba(213,43,30,.1); }
.field.invalid .err { display: block; }
.hack-form .btn { width: 100%; justify-content: center; margin-top: 6px; }
.hack-form .btn:disabled { opacity: .7; cursor: wait; transform: none; }
.form-error { display: none; color: var(--py-red); font-size: .82rem; margin-top: 12px; text-align: center; font-weight: 600; }
.form-error.show { display: block; }
.form-note { font-size: .72rem; color: var(--muted); margin-top: 12px; text-align: center; }
.form-success { display: none; text-align: center; padding: 24px 6px; }
.form-success.show { display: block; animation: pop .45s cubic-bezier(.2,.7,.2,1); }
@keyframes pop { from { transform: scale(.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.form-success .ok { width: 64px; height: 64px; border-radius: 50%; background: linear-gradient(135deg, var(--mint), var(--mint-dark)); display: grid; place-items: center; margin: 0 auto 16px; color: #fff; }
.form-success h3 { margin-bottom: 8px; }
.form-success p { font-size: .88rem; color: var(--muted); }
/* ---------- Responsive ---------- */
@media (max-width: 960px) {
.hero-grid { grid-template-columns: 1fr; gap: 40px; }
.hero-visual { height: 400px; max-width: 460px; margin: 0 auto; width: 100%; }
.tips-wrap { grid-template-columns: 1fr; }
.news-grid { grid-template-columns: repeat(2, 1fr); }
.state-grid { grid-template-columns: 1fr; }
.hack-grid { grid-template-columns: 1fr; }
.hack-targets { grid-template-columns: 1fr; }
.join-grid { grid-template-columns: 1fr; }
.foot-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 720px) {
nav ul { position: fixed; top: 72px; right: 0; left: 0; flex-direction: column; align-items: stretch;
background: #fff; padding: 18px; gap: 4px; border-bottom: 1px solid var(--line);
transform: translateY(-140%); transition: transform .35s cubic-bezier(.2,.7,.2,1); box-shadow: var(--shadow); }
nav ul.show { transform: translateY(0); }
nav a.link { display: block; padding: 12px 16px; }
.burger { display: block; }
.burger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.open span:nth-child(2) { opacity: 0; }
.burger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
.nav-cta { display: none; }
.news-grid { grid-template-columns: 1fr; }
.event { grid-template-columns: 70px 1fr; gap: 16px; padding: 18px; }
.event-date { padding-right: 14px; }
.event-date .d { font-size: 1.5rem; }
.event .badge { grid-column: 2; justify-self: start; margin-top: 6px; }
.join-box { padding: 34px 24px; }
.hack-box { padding: 34px 24px; }
.hack-form { padding: 24px 20px; }
.foot-grid { grid-template-columns: 1fr; }
section { padding: 54px 0; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation: none !important; transition: none !important; }
.reveal { opacity: 1; transform: none; }
}
</style>
</head>
<body>
<div class="bg-blobs" aria-hidden="true">
<div class="blob b1"></div>
<div class="blob b2"></div>
<div class="blob b3"></div>
</div>
<!-- ============ NAV ============ -->
<header class="nav" id="nav">
<div class="container nav-inner">
<a href="#top" class="brand">
<span class="logo logo-img"><img src="https://www.odoo-community.org/web/image/3657236-414fdacd/Community%20Badge%20Medium.png" alt="Logo de la Odoo Community Association" /></span>
<span>OCA Paraguay
<small>Comunidad Odoo · Paraguay</small>
</span>
</a>
<nav>
<ul id="menu">
<li><a class="link" href="#comunidad">Comunidad</a></li>
<li><a class="link" href="#estado">Estado Odoo</a></li>
<li><a class="link" href="#hackathon">Hackathon</a></li>
<li><a class="link" href="#noticias">Noticias</a></li>
<li><a class="link" href="#tips">Tips &amp; Trucos</a></li>
<li><a class="link" href="#eventos">Eventos</a></li>
<li><a class="link" href="#sumate">Sumate</a></li>
</ul>
</nav>
<div class="nav-cta">
<a href="https://www.odoo-community.org/get-involved/become-a-member" class="btn btn-primary" target="_blank" rel="noopener">Hacete miembro de la OCA</a>
</div>
<button class="burger" id="burger" aria-label="Abrir menú" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
</header>
<main id="top">
<!-- ============ HERO ============ -->
<section class="hero">
<div class="container hero-grid">
<div class="hero-copy">
<span class="pill"><span class="dot"></span> Comunidad hispanohablante en crecimiento</span>
<h1>El código abierto se <span class="grad-text">construye en comunidad</span>.</h1>
<p class="lead">
Somos la comunidad paraguaya de la <strong>Odoo Community Association</strong>.
Compartimos módulos, conocimiento y experiencia para que el ERP libre crezca en
Paraguay y en toda Latinoamérica.
</p>
<div class="hero-cta">
<a href="#sumate" class="btn btn-primary">
Hacete miembro
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
</a>
<a href="#tips" class="btn btn-ghost">Ver tips &amp; trucos</a>
</div>
<div class="hero-meta">
<div><strong data-count="20000" data-suffix="+">0</strong><span>Módulos OCA</span></div>
<div><strong data-count="62" data-suffix="">0</strong><span>Países</span></div>
<div><strong data-count="1600" data-suffix="+">0</strong><span>Colaboradores</span></div>
</div>
</div>
<div class="hero-visual" aria-hidden="true">
<div class="card-float cf-1">
<div class="cf-ico ico-violet">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M20 7 12 3 4 7l8 4 8-4Z"/><path d="m4 12 8 4 8-4M4 17l8 4 8-4"/></svg>
</div>
<h4>+20.000 módulos</h4>
<p>Probados y mantenidos por la comunidad, listos para Paraguay.</p>
</div>
<div class="card-float cf-2">
<div class="cf-ico ico-mint">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3.13a4 4 0 0 1 0 7.75"/><path d="M21 21v-2a4 4 0 0 0-3-3.87"/><circle cx="9" cy="7" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 2 .54"/></svg>
</div>
<h4>Peer review real</h4>
<p>No reinventés la rueda: extendé y colaborá.</p>
<div class="progress"><i></i></div>
</div>
<div class="card-float cf-3">
<div class="cf-ico ico-plum">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 6v6c0 5 3.4 8.5 8 10 4.6-1.5 8-5 8-10V6l-8-4Z"/><path d="m9 12 2 2 4-4"/></svg>
</div>
<h4>100% open source</h4>
<p>Independiente del editor. Licencia AGPL garantizada.</p>
</div>
</div>
</div>
</section>
<!-- ============ ESTADO ODOO CE PY ============ -->
<section id="estado">
<div class="container">
<div class="sec-head reveal">
<span class="kicker">Estado actual</span>
<h2>Odoo Community en Paraguay, hoy</h2>
<p>Un panorama del repositorio oficial <strong>OCA/l10n-paraguay</strong>, la localización de Odoo Community mantenida por la comunidad para el país.</p>
</div>
<div class="state-grid">
<div class="state-facts reveal">
<div class="fact">
<span class="fi ico-violet"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.9a3.4 3.4 0 0 0-.9-2.6c3-.3 6.2-1.5 6.2-6.7A5.2 5.2 0 0 0 20 5.8a4.9 4.9 0 0 0-.1-3.5s-1.1-.3-3.6 1.4a12.3 12.3 0 0 0-6.6 0C7.2 2 6.1 2.3 6.1 2.3A4.9 4.9 0 0 0 6 5.8a5.2 5.2 0 0 0-1.4 3.6c0 5.2 3.2 6.4 6.2 6.7A3.4 3.4 0 0 0 10 18.7V22"/></svg></span>
<div>
<h4>Localización oficial bajo el ala de la OCA</h4>
<p>El repo <code>OCA/l10n-paraguay</code> agrupa la localización paraguaya y se distribuye bajo licencia <strong>AGPL-3.0</strong>.</p>
</div>
</div>
<div class="fact">
<span class="fi ico-plum"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v12"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg></span>
<div>
<h4>Una sola rama activa: <code>16.0</code></h4>
<p>Hoy la localización cubre <strong>Odoo 16</strong>. Migrar a <code>17.0</code> y <code>18.0</code> es el gran trabajo pendiente de la comunidad.</p>
</div>
</div>
<div class="fact">
<span class="fi ico-mint"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v16H4z"/><path d="M8 9h8M8 13h8M8 17h5"/></svg></span>
<div>
<h4>Base contable local</h4>
<p>Plan contable, impuestos y datos base del Paraguay viven en <code>l10n_py</code> y <code>l10n_py_base</code>, con extensiones en <code>l10n_py_account</code>.</p>
</div>
</div>
<div class="fact">
<span class="fi ico-violet"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6Z"/><path d="M14 2v6h6M9 15l2 2 4-4"/></svg></span>
<div>
<h4>Facturación electrónica SIFEN</h4>
<p>Los módulos <code>l10n_py_edi_base</code> y <code>l10n_py_edi_sifen</code> permiten la <strong>transmisión directa al SIFEN</strong> usando la librería <code>pysifen</code>.</p>
</div>
</div>
</div>
<div class="mod-panel reveal" style="transition-delay:.1s">
<div class="mod-head">
<h3>Módulos disponibles</h3>
<span class="branch-pill"><span class="dot"></span> Rama activa: 16.0</span>
</div>
<ul class="mod-list">
<li>
<div><span class="name">l10n_py</span><div class="desc">Localización contable para Paraguay</div></div>
<span class="ver">16.0.1.1.0</span>
</li>
<li>
<div><span class="name">l10n_py_account</span><div class="desc">Extensiones contables para Paraguay</div></div>
<span class="ver">16.0.2.0.0</span>
</li>
<li>
<div><span class="name">l10n_py_base</span><div class="desc">Datos base de localización</div></div>
<span class="ver">16.0.1.2.0</span>
</li>
<li>
<div><span class="name">l10n_py_edi_base</span><div class="desc">Base para facturación electrónica</div></div>
<span class="ver">16.0.9.0.0</span>
</li>
<li>
<div><span class="name">l10n_py_edi_sifen</span><div class="desc">Transmisión directa al SIFEN (pysifen)</div></div>
<span class="ver">16.0.13.0.0</span>
</li>
</ul>
<div class="mod-foot"><strong>Help wanted:</strong> todavía no existe rama para <code>17.0</code> ni <code>18.0</code> — contribuir a la migración es una de las tareas más valiosas para la comunidad. <a href="https://github.com/OCA/l10n-paraguay" target="_blank" rel="noopener">Colaborá en GitHub →</a></div>
</div>
</div>
</div>
</section>
<!-- ============ HACKATHON ============ -->
<section id="hackathon">
<div class="container">
<div class="hack-box reveal">
<div class="hack-inner">
<div class="hack-grid">
<div class="hack-copy">
<span class="hack-badge"><span class="dot"></span> Online · Internacional · Gratis</span>
<h2>Hackathon de <span class="grad-text">Internacionalización</span> para Paraguay</h2>
<p>
Dos jornadas online para llevar la localización paraguaya de Odoo al siguiente nivel.
Sumate a desarrollar, testear, documentar o traducir junto a la comunidad.
</p>
<div class="hack-dates">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
12 y 14 de noviembre de 2026
</div>
<div class="countdown" id="countdown" aria-label="Cuenta regresiva al hackathon">
<div class="cd-unit"><strong id="cd-days">--</strong><span>Días</span></div>
<div class="cd-unit"><strong id="cd-hours">--</strong><span>Horas</span></div>
<div class="cd-unit"><strong id="cd-mins">--</strong><span>Min</span></div>
<div class="cd-unit"><strong id="cd-secs">--</strong><span>Seg</span></div>
</div>
</div>
<div class="hack-form">
<form id="hack-form" novalidate>
<h3>Inscribite al hackathon</h3>
<p>Dejanos tus datos y te avisamos antes del evento. Cupos abiertos para todos los niveles.</p>
<div class="field" id="f-name">
<label for="name">Nombre completo</label>
<input type="text" id="name" name="name" placeholder="Tu nombre" autocomplete="name" required />
<span class="err">Ingresá tu nombre.</span>
</div>
<div class="field" id="f-github">
<label for="github">Usuario de GitHub</label>
<input type="text" id="github" name="github" placeholder="@tu-usuario" autocomplete="off" required />
<span class="err">Ingresá tu usuario de GitHub.</span>
</div>
<div class="field" id="f-email">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="tu@email.com" autocomplete="email" required />
<span class="err">Ingresá un email válido.</span>
</div>
<div class="field" id="f-desc">
<label for="desc">Contanos brevemente cómo podés aportar</label>
<textarea id="desc" name="desc" placeholder="Ej.: puedo migrar módulos a Odoo 19, escribir tests o traducir cadenas al español de Paraguay." required></textarea>
<span class="err">Contanos brevemente tu aporte.</span>
</div>
<button type="submit" class="btn btn-primary" id="hack-submit">
Quiero inscribirme
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
</button>
<p class="form-error" id="form-error">No pudimos enviar tu inscripción. Revisá tu conexión e intentá de nuevo.</p>
<p class="form-note">Tus datos solo se usan para coordinar el hackathon.</p>
</form>
<div class="form-success" id="form-success" role="status" aria-live="polite">
<div class="ok">
<svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg>
</div>
<h3>¡Inscripción recibida!</h3>
<p>Gracias, <strong id="success-name">colega</strong>. Te vamos a contactar por email con los detalles del hackathon del 12 y 14 de noviembre de 2026.</p>
</div>
</div>
</div>
<div class="hack-goals">
<h3>Nuestros Objetivos</h3>
<div class="hack-targets">
<div class="hack-target">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>
<div>
<h4>Actualización de módulos a Odoo 19 y 20</h4>
<p>Migrar la localización actual (rama 16.0) a las versiones más recientes del ERP.</p>
</div>
</div>
<div class="hack-target">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/><circle cx="12" cy="12" r="9"/></svg>
<div>
<h4>Nuevas funcionalidades (Feature add)</h4>
<p>Incorporar features que la comunidad paraguaya necesita en su día a día.</p>
</div>
</div>
<div class="hack-target">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
<div>
<h4>Testing y documentación</h4>
<p>Pruebas, cobertura y guías claras para que más gente adopte Odoo en Paraguay.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ COMMUNITY ============ -->
<section id="comunidad" class="community">
<div class="container">
<div class="sec-head center reveal">
<span class="kicker">Quiénes somos</span>
<h2>Una red local con impacto global</h2>
<p>
La OCA es una organización sin fines de lucro con sede en Suiza que reúne a
desarrolladores, consultores y usuarios de Odoo en todo el mundo. Desde Paraguay
aportamos módulos, traducciones y comunidad para toda la región.
</p>
</div>
<div class="news-grid" style="grid-template-columns:repeat(3,1fr);">
<div class="news-card reveal" style="overflow:visible;border:none;box-shadow:none;background:transparent;">
<div class="cf-ico ico-violet" style="width:52px;height:52px;border-radius:15px;">
<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4M12 18v4M4.9 4.9l2.8 2.8M16.3 16.3l2.8 2.8M2 12h4M18 12h4M4.9 19.1l2.8-2.8M16.3 7.7l2.8-2.8"/></svg>
</div>
<h3 style="margin:16px 0 10px;font-size:1.2rem;">Colaboración abierta</h3>
<p style="color:var(--muted);font-size:.92rem;">Revisión entre pares, guías y sprints de código para mejorar el ecosistema Odoo.</p>
</div>
<div class="news-card reveal" style="overflow:visible;border:none;box-shadow:none;background:transparent;transition-delay:.1s">
<div class="cf-ico ico-mint" style="width:52px;height:52px;border-radius:15px;">
<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20Z"/></svg>
</div>
<h3 style="margin:16px 0 10px;font-size:1.2rem;">Adaptación local</h3>
<p style="color:var(--muted);font-size:.92rem;">Localización paraguaya: facturación electrónica, SIFEN y procesos propios del país.</p>
</div>
<div class="news-card reveal" style="overflow:visible;border:none;box-shadow:none;background:transparent;transition-delay:.2s">
<div class="cf-ico ico-plum" style="width:52px;height:52px;border-radius:15px;">
<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>
</div>
<h3 style="margin:16px 0 10px;font-size:1.2rem;">Formación y soporte</h3>
<p style="color:var(--muted);font-size:.92rem;">Webinars, sesiones de soporte para consultores y capacitaciones técnicas gratuitas.</p>
</div>
</div>
</div>
</section>
<!-- ============ NEWS ============ -->
<section id="noticias">
<div class="container">
<div class="sec-head reveal">
<span class="kicker">Noticias</span>
<h2>Lo último de la OCA y de la escena local</h2>
<p>Novedades de la comunidad internacional y de nuestro capítulo paraguayo.</p>
</div>
<div class="news-grid">
<article class="news-card reveal">
<div class="news-top nt-1">
<span class="tag">Evento global</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
</div>
<div class="news-body">
<div class="news-date">21 – 23 de septiembre, 2026</div>
<h3>OCA Days 2026 vuelve a Liège</h3>
<p>El encuentro anual de la OCA reúne a miembros de todo el mundo para colaborar, intercambiar conocimiento y ampliar la red. Ya está abierta la inscripción.</p>
<a class="more" href="#eventos">Ver detalles <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
<article class="news-card reveal" style="transition-delay:.08s">
<div class="news-top nt-2">
<span class="tag">Apps Store</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"/></svg>
</div>
<div class="news-body">
<div class="news-date">Julio 2026</div>
<h3>Nueva Apps Store de la OCA ya está online</h3>
<p>La nueva plataforma de módulos es más que una tienda: integra búsqueda mejorada, perfiles y mucha más información para elegir el módulo correcto.</p>
<a class="more" href="#tips">Cómo aprovecharla <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
<article class="news-card reveal" style="transition-delay:.16s">
<div class="news-top nt-3">
<span class="tag">Gobernanza</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M9 9h6v6H9zM9 2v2M15 2v2M9 20v2M15 20v2M2 9h2M2 15h2M20 9h2M20 15h2"/></svg>
</div>
<div class="news-body">
<div class="news-date">Agosto 2026</div>
<h3>Nueva Política de IA de la OCA</h3>
<p>El grupo de gobernanza publicó una política para ordenar las contribuciones asistidas por IA y mantener la sostenibilidad de las revisiones de código.</p>
<a class="more" href="#noticias">Leer resumen <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
<article class="news-card reveal">
<div class="news-top nt-4">
<span class="tag">Paraguay</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 2 7l10 5 10-5-10-5ZM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
</div>
<div class="news-body">
<div class="news-date">Comunidad local</div>
<h3>Primer sprint OCA Paraguay en Asunción</h3>
<p>Arrancamos con un encuentro presencial para localizar módulos clave y sumar colaboradores paraguayos al ecosistema internacional.</p>
<a class="more" href="#eventos">Anotate <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
<article class="news-card reveal" style="transition-delay:.08s">
<div class="news-top nt-2">
<span class="tag">Recursos</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/></svg>
</div>
<div class="news-body">
<div class="news-date">Julio 2026</div>
<h3>Lista de módulos "Must Have" de Stock</h3>
<p>La OCA publicó su selección de módulos imprescindibles para inventario y productos, una base ideal para cualquier implementación.</p>
<a class="more" href="#tips">Ver selección <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
<article class="news-card reveal" style="transition-delay:.16s">
<div class="news-top nt-1">
<span class="tag">Contribución</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v8M8 12h8"/></svg>
</div>
<div class="news-body">
<div class="news-date">Siempre abierto</div>
<h3>Cómo contribuir tu primer módulo</h3>
<p>Forkeá, creá tu rama por versión, seguí las guías de estilo y abrí un Pull Request. La comunidad te acompaña en cada revisión.</p>
<a class="more" href="#sumate">Empezar ahora <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</article>
</div>
</div>
</section>
<!-- ============ TIPS ============ -->
<section id="tips">
<div class="container">
<div class="sec-head reveal">
<span class="kicker">Tips &amp; Trucos</span>
<h2>Atajos que te ahorran horas</h2>
<p>Pequeños hábitos que marcan la diferencia al trabajar con módulos de la comunidad.</p>
</div>
<div class="tips-wrap">
<div class="tip-list reveal">
<div class="tip open" tabindex="0">
<div class="tip-head">
<span class="tip-num">01</span>
<h4>Elegí siempre la rama por versión</h4>
<svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
<div class="tip-body"><p>Los repositorios de la OCA usan ramas como <code>17.0</code>, <code>18.0</code>. Nunca uses <code>master</code> en producción: cada versión de Odoo tiene su propia rama estable y probada.</p></div>
</div>
<div class="tip" tabindex="0">
<div class="tip-head">
<span class="tip-num">02</span>
<h4>Automatizá dependencias con oca_dependencies</h4>
<svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
<div class="tip-body"><p>El archivo <code>oca_dependencies.txt</code> declara los repos de los que depende tu módulo. Herramientas como <em>oca_dependencies</em> clonan todo automáticamente y evitan "import errors" misteriosos.</p></div>
</div>
<div class="tip" tabindex="0">
<div class="tip-head">
<span class="tip-num">03</span>
<h4>Instalá con pip y no desde el zip</h4>
<svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
<div class="tip-body"><p>Cloná el repositorio completo y agregá la carpeta al <code>addons_path</code>. Instalar un módulo aislado suele romper dependencias entre módulos del mismo repo.</p></div>
</div>
<div class="tip" tabindex="0">
<div class="tip-head">
<span class="tip-num">04</span>
<h4>Usá pre-commit antes de cada PR</h4>
<svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
<div class="tip-body"><p>La configuración de <code>pre-commit</code> de la OCA valida formato, imports y estilo. Correrla localmente (o con el repo <code>oca-ci</code>) hace que tu PR se apruebe mucho más rápido.</p></div>
</div>
</div>
<aside class="tip-side reveal" style="transition-delay:.1s">
<h3>Reglas de oro de la comunidad</h3>
<p>Antes de escribir código nuevo, preguntate si ya existe. La mayoría de las veces, alguien de la OCA ya resolvió el problema.</p>
<ul>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg> Extendé, no reemplaces: usá <code style="color:#c6f3e6">_inherit</code> con criterio.</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg> Un módulo, una responsabilidad: evitá el "módulo Frankenstein".</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg> Documentá migraciones y respetá la licencia AGPL.</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg> Traducí: cada cadena en español suma para toda la región.</li>
</ul>
</aside>
</div>
</div>
</section>
<!-- ============ EVENTS ============ -->
<section id="eventos" class="events">
<div class="container">
<div class="sec-head reveal">
<span class="kicker">Eventos</span>
<h2>Próximos encuentros</h2>
<p>Webinars globales, sesiones de soporte y encuentros presenciales en Paraguay.</p>
</div>
<div class="event-list">
<article class="event reveal">
<div class="event-date"><div class="d">21</div><div class="m">SEP</div></div>
<div class="event-info">
<h3>OCA Days 2026 — Liège, Bélgica</h3>
<p>El evento anual de la comunidad: charlas, sprints de código y networking con miembros de todo el mundo.</p>
<span class="loc"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg> Palais des Congrès · 21–23 de septiembre</span>
</div>
<span class="badge presencial">Presencial</span>
</article>
<article class="event reveal" style="transition-delay:.06s">
<div class="event-date"><div class="d">01</div><div class="m">OCT</div></div>
<div class="event-info">
<h3>Webinar | Módulos OCA "Must Have" para Inventario</h3>
<p>Recorrido práctico por los módulos de Stock y Productos que toda implementación debería conocer.</p>
<span class="loc"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20"/></svg> Online · Sesión en vivo</span>
</div>
<span class="badge">Webinar</span>
</article>
<article class="event reveal" style="transition-delay:.12s">
<div class="event-date"><div class="d">08</div><div class="m">OCT</div></div>
<div class="event-info">
<h3>Sesión de Soporte OCA | Consultores</h3>
<p>Espacio abierto para llevar tus dudas técnicas y funcionales ante la comunidad internacional.</p>
<span class="loc"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20"/></svg> Online · Grupo de consultores</span>
</div>
<span class="badge">Soporte</span>
</article>
<article class="event reveal" style="transition-delay:.18s">
<div class="event-date"><div class="d">15</div><div class="m">NOV</div></div>
<div class="event-info">
<h3>Sprint OCA Paraguay — Asunción</h3>
<p>Encuentro local para localizar módulos, traducir cadenas al español de Paraguay y sumar nuevos colaboradores.</p>
<span class="loc"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg> Asunción, Paraguay</span>
</div>
<span class="badge taller">Taller</span>
</article>
</div>
</div>
</section>
<!-- ============ JOIN ============ -->
<section id="sumate">
<div class="container">
<div class="join-box reveal">
<div class="join-grid">
<div>
<h2>Sumate a la comunidad Odoo de Paraguay</h2>
<p>
No necesitás ser desarrollador: hay lugar para consultores, usuarios, traductores y
entusiastas del software libre. Ayudanos a que Odoo siga siendo abierto, local y de todos.
</p>
<div class="join-actions">
<a href="#noticias" class="btn btn-white">Quiero ser miembro</a>
<a href="#tips" class="btn btn-outline">Contribuir código</a>
</div>
</div>
<div class="join-stats">
<div class="join-stat"><strong>+20.000</strong><span>Módulos open source</span></div>
<div class="join-stat"><strong>62</strong><span>Países conectados</span></div>
<div class="join-stat"><strong>100%</strong><span>Licencia AGPL libre</span></div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- ============ FOOTER ============ -->
<footer>
<div class="container">
<div class="foot-grid">
<div class="foot-brand">
<a href="#top" class="brand">
<span class="logo logo-img"><img src="https://www.odoo-community.org/web/image/3657236-414fdacd/Community%20Badge%20Medium.png" alt="Logo de la Odoo Community Association" /></span>
<span>OCA Paraguay<small>Comunidad Odoo · Paraguay</small></span>
</a>
<p>Comunidad paraguaya de la Odoo Community Association. Construimos, traducimos y compartimos software libre para ERP en toda Latinoamérica.</p>
<div class="socials">
<a href="#top" aria-label="GitHub"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.4-1.8-1.4-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.9 1.2 1.9 1.2 1.1 1.9 2.9 1.3 3.6 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.9 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0C17.3 5 18.3 5.3 18.3 5.3c.6 1.6.2 2.8.1 3.1.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .5Z"/></svg></a>
<a href="#top" aria-label="LinkedIn"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM2.5 9h5v12h-5V9Zm7.5 0h4.8v1.7h.1c.7-1.3 2.3-2.1 3.9-2.1 4.2 0 5 2.7 5 6.3V21h-5v-5.3c0-1.3 0-3-1.8-3s-2.1 1.4-2.1 2.9V21h-5V9Z"/></svg></a>
<a href="#top" aria-label="YouTube"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M23 12s0-3.8-.5-5.6a2.9 2.9 0 0 0-2-2C18.6 4 12 4 12 4s-6.6 0-8.5.5a2.9 2.9 0 0 0-2 2C1 8.2 1 12 1 12s0 3.8.5 5.6a2.9 2.9 0 0 0 2 2C5.4 20 12 20 12 20s6.6 0 8.5-.5a2.9 2.9 0 0 0 2-2C23 15.8 23 12 23 12ZM9.8 15.3V8.7l5.7 3.3-5.7 3.3Z"/></svg></a>
</div>
</div>
<div>
<h5>Comunidad</h5>
<ul>
<li><a href="#comunidad">Quiénes somos</a></li>
<li><a href="#estado">Estado Odoo en Paraguay</a></li>
<li><a href="#hackathon">Hackathon 2026</a></li>
<li><a href="#sumate">Hacete miembro</a></li>
<li><a href="#tips">Contribuir código</a></li>
<li><a href="#eventos">Eventos</a></li>
</ul>
</div>
<div>
<h5>Recursos</h5>
<ul>
<li><a href="#tips">Módulos Must Have</a></li>
<li><a href="#tips">Guías técnicas</a></li>
<li><a href="#noticias">Apps Store</a></li>
<li><a href="#noticias">Política de IA</a></li>
</ul>
</div>
<div>
<h5>OCA global</h5>
<ul>
<li><a href="#top">odoo-community.org</a></li>
<li><a href="#top">GitHub · OCA</a></li>
<li><a href="#top">CLA y licencias</a></li>
<li><a href="#top">Contacto</a></li>
</ul>
</div>
</div>
<div class="flag-strip" aria-hidden="true"></div>
<div class="foot-bottom" style="margin-top:24px;">
<span>© <span id="year">2026</span> OCA Paraguay · Comunidad local de la Odoo Community Association.</span>
<span>Hecho con ♥ y software libre · Odoo y OCA son marcas de sus respectivos titulares.</span>
</div>
</div>
</footer>
<script>
// Sticky nav shadow
const nav = document.getElementById('nav');
const onScroll = () => nav.classList.toggle('scrolled', window.scrollY > 12);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
// Mobile menu
const burger = document.getElementById('burger');
const menu = document.getElementById('menu');
burger.addEventListener('click', () => {
const open = menu.classList.toggle('show');
burger.classList.toggle('open', open);
burger.setAttribute('aria-expanded', String(open));
});
menu.querySelectorAll('a').forEach(a => a.addEventListener('click', () => {
menu.classList.remove('show');
burger.classList.remove('open');
burger.setAttribute('aria-expanded', 'false');
}));
// Reveal on scroll
const io = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
}, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
// Animated counters
const counters = document.querySelectorAll('[data-count]');
const cio = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (!e.isIntersecting) return;
const el = e.target;
const target = parseInt(el.dataset.count, 10);
const suffix = el.dataset.suffix || '';
const duration = 1400;
const start = performance.now();
const tick = (now) => {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3);
const val = Math.round(target * eased);
el.textContent = val.toLocaleString('es-PY') + suffix;
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
cio.unobserve(el);
});
}, { threshold: 0.4 });
counters.forEach(c => cio.observe(c));
// Tips accordion
document.querySelectorAll('.tip').forEach(tip => {
const toggle = () => {
const wasOpen = tip.classList.contains('open');
document.querySelectorAll('.tip').forEach(t => t.classList.remove('open'));
if (!wasOpen) tip.classList.add('open');
};
tip.addEventListener('click', toggle);
tip.addEventListener('keydown', (ev) => {
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); toggle(); }
});
});
// Year
document.getElementById('year').textContent = new Date().getFullYear();
// Subtle parallax on hero cards
const visual = document.querySelector('.hero-visual');
if (visual && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
const cards = visual.querySelectorAll('.card-float');
visual.addEventListener('mousemove', (e) => {
const r = visual.getBoundingClientRect();
const x = (e.clientX - r.left) / r.width - 0.5;
const y = (e.clientY - r.top) / r.height - 0.5;
cards.forEach((c, i) => {
const depth = (i + 1) * 8;
c.style.transform = `translate3d(${x * depth}px, ${y * depth}px, 0)`;
});
});
visual.addEventListener('mouseleave', () => {
cards.forEach(c => { c.style.transform = ''; });
});
}
// Hackathon countdown (12 Nov 2026)
const target = new Date('2026-11-12T00:00:00').getTime();
const cd = {
days: document.getElementById('cd-days'),
hours: document.getElementById('cd-hours'),
mins: document.getElementById('cd-mins'),
secs: document.getElementById('cd-secs')
};
const pad = (n) => String(n).padStart(2, '0');
const tickCountdown = () => {
const diff = target - Date.now();
if (diff <= 0) {
cd.days.textContent = cd.hours.textContent = cd.mins.textContent = cd.secs.textContent = '00';
return;
}
cd.days.textContent = Math.floor(diff / 86400000);
cd.hours.textContent = pad(Math.floor(diff / 3600000) % 24);
cd.mins.textContent = pad(Math.floor(diff / 60000) % 60);
cd.secs.textContent = pad(Math.floor(diff / 1000) % 60);
};
if (cd.days) { tickCountdown(); setInterval(tickCountdown, 1000); }
// Hackathon signup form
const form = document.getElementById('hack-form');
if (form) {
const WEBHOOK_URL = 'https://n8n.odoo4projects.com/webhook/OCA/hackathon';
const btn = document.getElementById('hack-submit');
const errorEl = document.getElementById('form-error');
const setInvalid = (id, bad) => document.getElementById(id).classList.toggle('invalid', bad);
form.addEventListener('submit', async (ev) => {
ev.preventDefault();
errorEl.classList.remove('show');
const name = form.querySelector('#name').value.trim();
const github = form.querySelector('#github').value.trim();
const email = form.querySelector('#email').value.trim();
const desc = form.querySelector('#desc').value.trim();
const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
setInvalid('f-name', !name);
setInvalid('f-github', !github);
setInvalid('f-email', !emailOk);
setInvalid('f-desc', !desc);
if (!name || !github || !emailOk || !desc) {
const firstBad = form.querySelector('.field.invalid input, .field.invalid textarea');
if (firstBad) firstBad.focus();
return;
}
const original = btn.innerHTML;
btn.disabled = true;
btn.textContent = 'Enviando…';
try {
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name,
github: github,
email: email,
description: desc,
event: 'hackathon-oca-py-2026',
source: window.location.href
})
});
if (!res.ok) throw new Error('HTTP ' + res.status);
document.getElementById('success-name').textContent = name.split(' ')[0];
form.style.display = 'none';
document.getElementById('form-success').classList.add('show');
} catch (err) {
console.error('Error al enviar la inscripción:', err);
btn.disabled = false;
btn.innerHTML = original;
errorEl.classList.add('show');
}
});
form.querySelectorAll('input, textarea').forEach(el => {
el.addEventListener('input', () => el.closest('.field')?.classList.remove('invalid'));
});
}
</script>
</body>
</html>