/* ══════════════════════════════════════════════════════════════
   SERP HAPPENS — Design Tokens "Terminal Editorial"
   ══════════════════════════════════════════════════════════════ */
:root {
  --font-display:'Bricolage Grotesque','Hanken Grotesk',system-ui,sans-serif;
  --font-body:'Hanken Grotesk',system-ui,-apple-system,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,monospace;
  --paper:#F2ECDE; --paper-raised:#FBF7EE; --paper-sunken:#E8E0CF;
  --ink:#1A1A15; --ink-soft:#565144; --ink-muted:#8B8576;
  --rule:#D8CFBC; --hairline:rgba(26,26,21,.13);
  --lime:#C8F03F; --lime-deep:#A7CE1F; --lime-ink:#44560A;
  --clay:#D5552C; --clay-deep:#B7431F; --clay-soft:#F3D9C8;
  --pine:#2F5A4C; --plum:#5A3B6E;
  --bg:var(--paper); --surface:var(--paper-raised); --surface-sunken:var(--paper-sunken);
  --fg:var(--ink); --fg-soft:var(--ink-soft); --fg-muted:var(--ink-muted);
  --border:var(--ink); --border-soft:var(--rule);
  --accent:var(--lime); --link:var(--clay-deep); --link-hover:var(--ink);
  --highlight:var(--lime); --focus-ring:var(--clay);
  --r-xs:3px; --r-sm:6px; --r-md:10px; --r-lg:16px; --r-pill:999px;
  --bd:1.5px; --bd-bold:2.5px;
  --shadow-sticker:4px 4px 0 0 var(--ink);
  --shadow-sticker-sm:3px 3px 0 0 var(--ink);
  --shadow-sticker-lime:4px 4px 0 0 var(--lime-deep);
  --shadow-soft:0 1px 2px rgba(26,26,21,.06),0 8px 24px -8px rgba(26,26,21,.18);
  --s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;
  --s-6:32px;--s-7:48px;--s-8:64px;--s-9:96px;--s-10:128px;
  --t-display:clamp(2.6rem,1.6rem + 4.2vw,4.5rem);
  --t-h2:clamp(2rem,1.4rem + 2.4vw,2.9rem);
  --t-h3:clamp(1.35rem,1.1rem + 1vw,1.65rem);
  --t-h4:1.15rem; --t-lead:clamp(1.1rem,1rem + .4vw,1.28rem);
  --t-body:1.0625rem; --t-small:0.9375rem; --t-kicker:0.75rem; --t-meta:0.8125rem;
  --lh-tight:1.02; --lh-snug:1.18; --lh-body:1.62; --tracking-kicker:0.14em;
}
[data-theme="dark"] {
  --bg:#14140E; --surface:#1E1E15; --surface-sunken:#0E0E09;
  --fg:#F2ECDE; --fg-soft:#B8B2A0; --fg-muted:#807B6B;
  --border:#F2ECDE; --border-soft:rgba(52,51,38,.27);
  --accent:#CBF24D; --link:#E97A50; --link-hover:#F2ECDE;
  --highlight:#CBF24D; --rule:#34332A; --hairline:rgba(242,236,222,.12);
  --shadow-sticker:4px 4px 0 0 #000; --shadow-sticker-sm:3px 3px 0 0 #000;
  --shadow-soft:0 1px 2px rgba(0,0,0,.4),0 10px 30px -10px rgba(0,0,0,.6);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;}
body{font-family:var(--font-body);background:var(--bg);color:var(--fg);
  font-size:var(--t-body);line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;}
img{max-width:100%;display:block;}
a{color:var(--link);text-decoration-thickness:1.5px;text-underline-offset:3px;}
a:hover{color:var(--link-hover);}
button{font-family:inherit;cursor:pointer;}
mark{background:linear-gradient(var(--highlight),var(--highlight)) no-repeat;
  background-size:100% 40%;background-position:0 88%;color:inherit;padding:0 .05em;}
code{font-family:var(--font-mono);font-size:.92em;background:var(--surface-sunken);
  padding:.12em .38em;border-radius:var(--r-xs);}
/* Dark mode: hero mark = laranja clay + lime brushstroke espesso */
[data-theme="dark"] .sh-hero__title mark{
  color:#f86c33;
  background:linear-gradient(var(--lime),var(--lime)) no-repeat;
  background-size:100% 48%;
  background-position:0 94%;
  padding:0 .05em;
  border-radius:0;
}
.sh-container{max-width:1200px;margin:0 auto;padding:0 var(--s-5);}
.sh-container--narrow{max-width:760px;}
/* Kicker */
.sh-kicker{display:inline-flex;align-items:center;font-family:var(--font-mono);
  font-weight:500;font-size:var(--t-kicker);text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);color:var(--clay-deep);}
.sh-kicker::before{content:"› ";color:var(--lime-deep);}
.sh-kicker--light{color:var(--lime);}
.sh-kicker--light::before{color:var(--lime);}
[data-theme="dark"] .sh-kicker{color:var(--link);}
[data-theme="dark"] .sh-kicker::before{color:var(--accent);}
/* Tags */
.sh-tag{display:inline-flex;align-items:center;font-family:var(--font-mono);
  font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  padding:3px 10px;border-radius:var(--r-pill);border:1.5px solid transparent;}
.sh-tag--perrengues{background:var(--clay-soft);color:var(--clay-deep);border-color:var(--clay-deep);}
.sh-tag--laboratorio{background:#d3ece6;color:var(--pine);border-color:var(--pine);}
.sh-tag--conteudo{background:#e8dff2;color:var(--plum);border-color:var(--plum);}
.sh-tag--dicas{background:var(--lime);color:var(--lime-ink);border-color:var(--lime-deep);}
.sh-tag--carreira{background:var(--paper-sunken);color:var(--fg-soft);border-color:var(--rule);}
/* Buttons */
.sh-btn{display:inline-flex;align-items:center;gap:var(--s-2);
  font-family:var(--font-body);font-size:var(--t-small);font-weight:600;
  padding:10px 20px;border-radius:var(--r-md);border:var(--bd) solid transparent;
  cursor:pointer;text-decoration:none;transition:transform 80ms ease,box-shadow 80ms ease;
  white-space:nowrap;}
.sh-btn--primary{background:var(--ink);color:var(--paper);border-color:var(--ink);box-shadow:var(--shadow-sticker-sm);}
.sh-btn--primary:hover{transform:translate(3px,3px);box-shadow:none;color:var(--paper);}
.sh-btn--secondary{background:transparent;color:var(--fg);border-color:var(--border);}
.sh-btn--secondary:hover{background:var(--surface-sunken);}
.sh-btn--lime{background:var(--lime);color:var(--lime-ink);border-color:var(--lime-deep);box-shadow:var(--shadow-sticker-lime);}
.sh-btn--lime:hover{transform:translate(4px,4px);box-shadow:none;color:var(--lime-ink);}
.sh-btn--sm{padding:6px 14px;font-size:13px;}
/* Byline */
.sh-byline{display:flex;align-items:center;gap:var(--s-2);}
.sh-avatar{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;font-family:var(--font-display);font-size:10px;font-weight:800;
  border:1.5px solid var(--ink);flex-shrink:0;}
.sh-byline__txt{font-family:var(--font-mono);font-size:var(--t-meta);color:var(--fg-muted);}
.sh-byline__name:hover{color:var(--clay-deep);text-decoration:underline;}
/* Header */
.sh-header{position:sticky;top:0;z-index:100;
  background:color-mix(in srgb,var(--bg) 92%,transparent);
  backdrop-filter:blur(10px);border-bottom:var(--bd) solid var(--border);}
.sh-header__bar{display:flex;align-items:center;gap:var(--s-4);
  padding:var(--s-3) var(--s-5);max-width:1200px;margin:0 auto;}
.sh-logo{display:flex;align-items:center;gap:var(--s-2);text-decoration:none;flex-shrink:0;}
.sh-logo__mark{height:32px;width:auto;}
.sh-logo__wm{font-family:var(--font-display);font-weight:800;font-size:1.1rem;
  letter-spacing:-0.02em;color:var(--fg);line-height:1;}
.sh-logo__accent{color:var(--clay);}
.sh-nav{display:flex;gap:0;margin-left:auto;}
.sh-nav__link{font-family:var(--font-mono);font-size:var(--t-kicker);font-weight:500;
  text-transform:uppercase;letter-spacing:var(--tracking-kicker);color:var(--fg-soft);
  text-decoration:none;padding:var(--s-2) var(--s-3);border-radius:var(--r-sm);
  transition:color 80ms,background 80ms;}
.sh-nav__link:hover,.sh-nav__link.is-active{color:var(--fg);background:var(--surface-sunken);}
.sh-nav__link.is-active::before{content:"› ";color:var(--lime-deep);}
.sh-header__actions{display:flex;align-items:center;gap:var(--s-2);flex-shrink:0;}
.sh-iconbtn{width:34px;height:34px;border-radius:var(--r-sm);
  border:var(--bd) solid var(--border-soft);background:transparent;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;color:var(--fg-soft);transition:background 80ms,color 80ms;}
.sh-iconbtn:hover{background:var(--surface-sunken);color:var(--fg);}
.sh-burger{display:none !important;}
.sh-mobnav{display:flex;flex-direction:column;border-top:var(--bd) solid var(--border-soft);
  padding:var(--s-3) var(--s-5);gap:var(--s-1);background:var(--bg);}
.sh-mobnav a{font-family:var(--font-mono);font-size:var(--t-kicker);text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);color:var(--fg-soft);text-decoration:none;
  padding:var(--s-2) 0;border-bottom:1px solid var(--hairline);}
.sh-mobnav a:hover{color:var(--fg);}
/* Hero */
.sh-hero{padding:var(--s-9) 0 var(--s-7);}
.sh-hero__grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-8);align-items:center;}
.sh-hero__left{display:flex;flex-direction:column;gap:var(--s-4);}
.sh-hero__title{font-family:var(--font-display);font-weight:800;
  font-size:clamp(2.8rem,1.4rem + 5vw,4.4rem);line-height:1.02;letter-spacing:-0.03em;}
.sh-hero__dek{font-size:var(--t-lead);color:var(--fg-soft);line-height:1.55;max-width:44ch;}
.sh-hero__cta{display:flex;gap:var(--s-3);flex-wrap:wrap;align-items:center;}
.sh-hero__right{display:flex;flex-direction:column;gap:var(--s-3);}
.sh-hero__stats{display:flex;gap:var(--s-2);align-items:center;flex-wrap:wrap;
  font-family:var(--font-mono);font-size:var(--t-meta);color:var(--fg-muted);padding:0 var(--s-1);}
/* SERP Snippet */
.sh-serp-snippet{position:relative;background:var(--surface);border:var(--bd) solid var(--border);
  box-shadow:var(--shadow-sticker);border-radius:var(--r-lg);
  padding:var(--s-5) var(--s-5) var(--s-4);cursor:pointer;
  transition:transform 80ms ease,box-shadow 80ms ease;}
.sh-serp-snippet:hover{transform:translate(4px,4px);box-shadow:none;}
.sh-serp-snippet__badge{position:absolute;top:-12px;left:var(--s-5);
  font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase;
  letter-spacing:.1em;background:var(--lime);color:var(--lime-ink);
  padding:2px 10px;border-radius:var(--r-pill);border:1.5px solid var(--lime-deep);}
.sh-serp-snippet__header{display:flex;align-items:center;gap:var(--s-2);margin-bottom:var(--s-2);}
.sh-serp-snippet__favicon{width:26px;height:26px;border-radius:50%;
  background:var(--lime);border:1.5px solid var(--ink);display:flex;
  align-items:center;justify-content:center;font-family:var(--font-display);
  font-size:12px;font-weight:800;color:var(--lime-ink);flex-shrink:0;}
.sh-serp-snippet__url-wrap{display:flex;flex-direction:column;gap:1px;min-width:0;}
.sh-serp-snippet__domain{font-family:var(--font-mono);font-size:12px;color:var(--clay-deep);}
.sh-serp-snippet__breadcrumb{font-family:var(--font-mono);font-size:11px;color:var(--fg-muted);}
.sh-serp-snippet__dots{margin-left:auto;color:var(--fg-muted);font-size:18px;line-height:1;}
.sh-serp-snippet__title{font-family:var(--font-display);font-weight:700;font-size:1.05rem;
  color:var(--clay-deep);line-height:1.3;margin-bottom:var(--s-2);
  text-decoration:underline;text-decoration-color:transparent;text-underline-offset:2px;
  transition:text-decoration-color 80ms;}
.sh-serp-snippet:hover .sh-serp-snippet__title{text-decoration-color:var(--clay-deep);}
.sh-serp-snippet__desc{font-size:13.5px;color:var(--fg-soft);line-height:1.55;margin-bottom:var(--s-3);}
.sh-serp-snippet__sitelinks{display:flex;flex-wrap:wrap;gap:6px;}
.sh-serp-snippet__sitelink{font-family:var(--font-mono);font-size:11px;
  border:1px solid var(--border-soft);border-radius:var(--r-sm);
  padding:3px 10px;color:var(--clay-deep);background:var(--bg);
  cursor:pointer;transition:background 80ms,color 80ms;}
.sh-serp-snippet__sitelink:hover{background:var(--lime);color:var(--lime-ink);border-color:var(--lime-deep);}
/* Duotone */
.sh-duotone{position:relative;overflow:hidden;border-bottom:var(--bd) solid var(--border);}
.sh-duotone:not(.sh-duotone--tall){aspect-ratio:16/9;}
.sh-duotone--tall{aspect-ratio:3/2;}
/* Cards */
.sh-card{background:var(--surface);border:var(--bd) solid var(--border);
  box-shadow:var(--shadow-sticker);border-radius:var(--r-lg);overflow:hidden;
  cursor:pointer;display:flex;flex-direction:column;
  transition:transform 80ms ease,box-shadow 80ms ease;}
.sh-card:hover{transform:translate(4px,4px);box-shadow:none;}
.sh-card__body{padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-2);flex:1;}
.sh-card__title{font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);
  line-height:1.2;letter-spacing:-0.015em;text-wrap:pretty;}
.sh-card__title--lg{font-size:var(--t-h2);}
.sh-card__dek{font-size:var(--t-small);color:var(--fg-soft);line-height:1.55;text-wrap:pretty;}
.sh-card .sh-byline{margin-top:auto;padding-top:var(--s-2);}
/* Section head */
.sh-section{padding:var(--s-7) 0;}
.sh-section--muted{background:var(--surface-sunken);}
.sh-section-head{display:flex;align-items:baseline;gap:var(--s-3);
  margin-bottom:var(--s-6);padding-bottom:var(--s-3);
  border-bottom:var(--bd) solid var(--border-soft);}
.sh-section-head__prompt{font-family:var(--font-mono);font-size:var(--t-kicker);
  text-transform:uppercase;letter-spacing:var(--tracking-kicker);color:var(--lime-deep);flex-shrink:0;}
.sh-section-head__title{font-family:var(--font-display);font-weight:800;
  font-size:var(--t-h2);letter-spacing:-0.02em;}
.sh-section-head__all{margin-left:auto;font-family:var(--font-mono);font-size:var(--t-kicker);
  color:var(--clay-deep);text-decoration:none;text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);flex-shrink:0;}
.sh-section-head__all:hover{color:var(--ink);}
/* Editorial grid */
.sh-editorial-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--s-5);}
.sh-card--featured{grid-column:span 8;}
.sh-editorial-sidebar{grid-column:span 4;display:flex;flex-direction:column;gap:var(--s-4);}
.sh-editorial-sidebar .sh-card{flex:1;}
.sh-card--grid3{grid-column:span 4;}
.sh-cards-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-5);}
.sh-list-grid{display:flex;flex-direction:column;gap:var(--s-3);}
.sh-list-grid .sh-card{flex-direction:row;border-radius:var(--r-md);}
.sh-list-grid .sh-card__body{flex-direction:row;align-items:center;flex-wrap:wrap;gap:var(--s-3);}
.sh-list-grid .sh-card__title{font-size:var(--t-h4);flex:1;min-width:200px;}
/* Editoria showcase */
.sh-editoria-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:var(--s-3);}
.sh-editoria-item{background:var(--surface);border:var(--bd) solid var(--border);
  box-shadow:var(--shadow-sticker-sm);border-radius:var(--r-md);
  padding:var(--s-4) var(--s-4) var(--s-3);cursor:pointer;
  transition:transform 80ms,box-shadow 80ms;}
.sh-editoria-item:hover{transform:translate(3px,3px);box-shadow:none;}
.sh-editoria-item__dot{width:12px;height:12px;border-radius:50%;
  border:1.5px solid var(--border);margin-bottom:var(--s-2);}
.sh-editoria-item__name{font-family:var(--font-display);font-weight:700;font-size:var(--t-h4);
  line-height:1.25;margin-bottom:var(--s-1);text-wrap:pretty;}
.sh-editoria-item__dek{font-size:13px;color:var(--fg-soft);line-height:1.45;text-wrap:pretty;}
.sh-editoria-item__count{font-family:var(--font-mono);font-size:11px;text-transform:uppercase;
  letter-spacing:.12em;color:var(--fg-muted);display:block;margin-top:var(--s-3);}
/* Mascot Divider */
.sh-mascot-divider{position:relative;height:88px;overflow:hidden;
  display:flex;align-items:center;background:var(--bg);}
.sh-mascot-divider__rule{position:absolute;left:0;right:0;top:66px;height:1.5px;background:var(--rule);}
.sh-mascot-divider__label{position:absolute;left:50%;top:66px;
  transform:translate(-50%,-50%);font-family:var(--font-mono);font-size:10px;
  text-transform:uppercase;letter-spacing:.2em;color:var(--fg-muted);
  background:var(--bg);padding:0 var(--s-3);white-space:nowrap;pointer-events:none;}
.sh-mascot-divider__track{position:absolute;inset:0;pointer-events:none;}
.sh-mascot-divider__bob{position:absolute;bottom:22px;animation:mascotTranslate 14s linear infinite;will-change:transform;}
.sh-mascot-divider--slow .sh-mascot-divider__bob{animation-duration:22s;}
.sh-mascot-divider--static .sh-mascot-divider__bob{animation:none;transform:translateX(40vw);}
.sh-mascot-divider--hidden{height:36px;}
.sh-mascot-divider--hidden .sh-mascot-divider__bob{display:none;}
.sh-mascot-divider__img{height:50px;width:auto;display:block;animation:mascotBob 360ms ease-in-out infinite;}
@keyframes mascotTranslate{from{transform:translateX(-80px);}to{transform:translateX(calc(100vw + 80px));}}
@keyframes mascotBob{0%,100%{transform:translateY(0);}50%{transform:translateY(-5px);}}
@media(prefers-reduced-motion:reduce){
  .sh-mascot-divider__bob{animation:none!important;transform:translateX(40vw)!important;}
  .sh-mascot-divider__img{animation:none!important;}
}
/* Newsletter */
.sh-newsletter{background:var(--ink);color:var(--paper);padding:var(--s-9) var(--s-5);}
.sh-newsletter__inner{max-width:1200px;margin:0 auto;
  display:grid;grid-template-columns:1fr 1fr;gap:var(--s-8);align-items:center;}
.sh-newsletter__title{font-family:var(--font-display);font-weight:800;
  font-size:var(--t-h2);letter-spacing:-0.02em;color:var(--paper);
  line-height:var(--lh-snug);margin:var(--s-3) 0;}
.sh-newsletter__dek{color:rgba(242,236,222,.7);line-height:var(--lh-body);}
.sh-newsletter__form{display:flex;flex-direction:column;gap:var(--s-3);}
.sh-newsletter__ok{display:flex;align-items:center;gap:var(--s-2);
  color:var(--lime);font-family:var(--font-mono);font-size:var(--t-small);}
.sh-input{background:rgba(242,236,222,.08);border:1.5px solid rgba(242,236,222,.2);
  color:var(--paper);font-family:var(--font-mono);font-size:var(--t-body);
  padding:12px 16px;border-radius:var(--r-md);outline:none;transition:border-color 160ms;width:100%;}
.sh-input:focus{border-color:var(--lime);}
.sh-input::placeholder{color:rgba(242,236,222,.35);}
.sh-input--light{background:var(--surface);border-color:var(--border);color:var(--fg);}
.sh-input--light::placeholder{color:var(--fg-muted);}
.sh-input--light:focus{border-color:var(--clay);}
/* Footer */
.sh-footer{background:var(--ink);color:var(--paper);padding:var(--s-8) var(--s-5) var(--s-5);}
.sh-footer__inner{max-width:1200px;margin:0 auto;display:grid;
  grid-template-columns:2fr 1fr 1fr;gap:var(--s-7);
  padding-bottom:var(--s-6);margin-bottom:var(--s-5);
  border-bottom:1px solid rgba(242,236,222,.1);}
.sh-footer__tag{font-size:var(--t-small);color:rgba(242,236,222,.55);
  line-height:1.6;margin:var(--s-3) 0 var(--s-4);max-width:36ch;}
.sh-footer__social{display:flex;gap:var(--s-2);}
.sh-footer__social a{color:rgba(242,236,222,.45);transition:color 80ms;text-decoration:none;}
.sh-footer__social a:hover{color:var(--lime);}
.sh-footer__col{display:flex;flex-direction:column;gap:var(--s-2);}
.sh-footer__col-head{font-family:var(--font-mono);font-size:11px;font-weight:700;
  text-transform:uppercase;letter-spacing:.15em;color:rgba(242,236,222,.35);margin-bottom:var(--s-1);}
.sh-footer__col a{font-size:var(--t-small);color:rgba(242,236,222,.65);text-decoration:none;transition:color 80ms;}
.sh-footer__col a:hover{color:var(--lime);}
.sh-footer__legal{max-width:1200px;margin:0 auto;display:flex;
  justify-content:space-between;align-items:center;flex-wrap:wrap;gap:var(--s-3);
  font-family:var(--font-mono);font-size:var(--t-meta);color:rgba(242,236,222,.3);}
.sh-footer__mono{color:rgba(200,240,63,.4);}
/* Table of Contents */
.sh-article-layout{display:grid;grid-template-columns:1fr 210px;gap:var(--s-8);
  align-items:start;max-width:1020px;margin:0 auto;padding:0 var(--s-5);}
.sh-toc{position:sticky;top:72px;}
.sh-toc__inner{background:var(--surface);border:var(--bd) solid var(--border-soft);
  border-radius:var(--r-lg);padding:var(--s-4);}
.sh-toc__head{font-family:var(--font-mono);font-size:var(--t-kicker);text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);color:var(--fg-muted);display:block;
  margin-bottom:var(--s-3);padding-bottom:var(--s-2);border-bottom:1px solid var(--hairline);}
.sh-toc__head::before{content:"› ";color:var(--lime-deep);}
.sh-toc__nav{display:flex;flex-direction:column;gap:2px;}
.sh-toc__link{font-size:13px;color:var(--fg-soft);text-decoration:none;
  padding:5px var(--s-3);border-left:2px solid transparent;
  border-radius:0 var(--r-xs) var(--r-xs) 0;line-height:1.4;
  transition:color 80ms,border-color 80ms,background 80ms;display:block;text-wrap:pretty;}
.sh-toc__link:hover{color:var(--fg);border-left-color:var(--border-soft);background:var(--surface-sunken);}
.sh-toc__link.is-active{color:var(--clay-deep);border-left-color:var(--clay);
  font-weight:600;background:var(--clay-soft);}
.sh-article-wrap{padding:var(--s-7) 0;}
.sh-article-back{max-width:1020px;margin:0 auto;padding:0 var(--s-5) var(--s-4);}
@media(max-width:900px){
  .sh-article-layout{grid-template-columns:1fr;}
  .sh-toc{position:static;order:-1;}
  .sh-toc__inner{display:flex;flex-direction:column;}
}
.sh-back{display:inline-flex;align-items:center;gap:var(--s-2);
  font-family:var(--font-mono);font-size:var(--t-kicker);text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);color:var(--fg-muted);background:none;
  border:none;cursor:pointer;margin-bottom:var(--s-5);padding:0;}
.sh-back:hover{color:var(--fg);}
.sh-article__head{margin-bottom:var(--s-6);}
.sh-article__title{font-family:var(--font-display);font-weight:800;
  font-size:var(--t-h2);line-height:var(--lh-snug);letter-spacing:-0.02em;
  margin:var(--s-3) 0;text-wrap:pretty;}
.sh-article__dek{font-size:var(--t-lead);color:var(--fg-soft);line-height:1.55;}
.sh-article .sh-byline{margin:var(--s-4) 0 0;padding:var(--s-3) 0;border-top:1px solid var(--hairline);}
.sh-article .sh-duotone{border-radius:var(--r-lg);overflow:hidden;margin:var(--s-5) 0;}
.sh-prose{margin-top:var(--s-5);}
.sh-prose p{line-height:var(--lh-body);margin-bottom:var(--s-4);font-size:var(--t-body);}
.sh-prose h2{font-family:var(--font-display);font-weight:700;font-size:var(--t-h2);
  letter-spacing:-0.02em;margin:var(--s-7) 0 var(--s-3);line-height:var(--lh-snug);}
.sh-prose h3{font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);
  letter-spacing:-0.01em;margin:var(--s-6) 0 var(--s-3);}
/* Callout */
.sh-callout{background:var(--clay-soft);border:1.5px solid var(--clay);
  border-radius:var(--r-lg);margin:var(--s-6) 0;
  box-shadow:3px 3px 0 0 var(--clay-deep);overflow:hidden;position:relative;}
.sh-callout__stripe{position:absolute;top:0;left:0;right:0;height:7px;
  background:repeating-linear-gradient(-45deg,var(--clay) 0,var(--clay) 8px,var(--clay-soft) 8px,var(--clay-soft) 16px);}
.sh-callout__inner{display:flex;gap:var(--s-4);
  padding:calc(var(--s-5) + 7px) var(--s-5) var(--s-4);align-items:flex-start;}
.sh-callout__mascot{height:44px;width:auto;flex-shrink:0;margin-top:2px;}
.sh-callout__lbl{font-family:var(--font-mono);font-size:11px;font-weight:700;
  text-transform:uppercase;letter-spacing:.12em;color:var(--clay-deep);display:block;margin-bottom:var(--s-1);}
.sh-callout__title{font-family:var(--font-display);font-weight:700;font-size:1rem;
  color:var(--clay-deep);line-height:1.3;margin-bottom:var(--s-2);}
.sh-callout__txt{font-size:var(--t-small);color:var(--fg-soft);line-height:var(--lh-body);}
/* Blockquote */
.sh-blockquote{border-left:4px solid var(--lime-deep);margin:var(--s-6) 0;
  padding:var(--s-4) var(--s-5);background:var(--surface);border-radius:0 var(--r-md) var(--r-md) 0;}
.sh-blockquote__q{display:block;font-family:var(--font-display);font-weight:700;
  font-size:var(--t-h3);line-height:1.3;margin-bottom:var(--s-2);letter-spacing:-0.01em;}
.sh-blockquote__by{font-family:var(--font-mono);font-size:var(--t-meta);color:var(--fg-muted);}
.sh-tags-row{display:flex;flex-wrap:wrap;gap:var(--s-2);
  margin-top:var(--s-5);padding-top:var(--s-5);border-top:1px solid var(--hairline);}
/* Editoria page */
.sh-editoria-page{padding:var(--s-7) 0;}
.sh-editoria-hero{padding:var(--s-6) 0;margin-bottom:var(--s-6);border-bottom:var(--bd) solid var(--border-soft);}
.sh-editoria-hero__title{font-family:var(--font-display);font-weight:800;
  font-size:clamp(2.2rem,1.5rem+3vw,4rem);line-height:1.0;
  letter-spacing:-0.03em;margin:var(--s-3) 0 var(--s-2);text-wrap:pretty;}
.sh-editoria-hero__dek{font-size:var(--t-lead);color:var(--fg-soft);max-width:50ch;}
/* Author page */
.sh-author-page{padding:var(--s-7) 0;}
.sh-author-card{background:var(--ink);color:var(--paper);border-radius:var(--r-lg);
  padding:var(--s-6);margin-bottom:var(--s-6);}
.sh-author-card__prompt{font-family:var(--font-mono);font-size:var(--t-meta);color:var(--lime);margin-bottom:var(--s-2);}
.sh-author-avatar-lg{width:72px;height:72px;border-radius:50%;display:flex;
  align-items:center;justify-content:center;font-family:var(--font-display);
  font-size:1.4rem;font-weight:800;border:2px solid rgba(242,236,222,.3);margin-bottom:var(--s-4);}
.sh-author-card__name{font-family:var(--font-display);font-size:var(--t-h2);
  font-weight:800;letter-spacing:-0.02em;color:var(--paper);line-height:1.1;margin-bottom:var(--s-1);}
.sh-author-card__role{font-family:var(--font-mono);color:var(--lime);font-size:var(--t-meta);margin-bottom:var(--s-3);}
.sh-author-card__bio{font-size:var(--t-small);color:rgba(242,236,222,.75);line-height:var(--lh-body);max-width:60ch;margin-bottom:var(--s-3);}
.sh-author-card__meta{font-family:var(--font-mono);font-size:var(--t-meta);color:rgba(242,236,222,.4);}
/* Manifesto */
.sh-manifesto-page{padding:var(--s-7) 0 var(--s-9);}
.sh-manifesto__title{font-family:var(--font-display);font-weight:800;
  font-size:clamp(2.4rem,1.5rem+4vw,4.5rem);line-height:1.02;
  letter-spacing:-0.03em;margin:var(--s-3) 0 var(--s-5);text-wrap:pretty;}
.sh-manifesto__lead{font-size:var(--t-lead);color:var(--fg-soft);line-height:1.6;max-width:56ch;margin-bottom:var(--s-7);}
.sh-manifesto__items{border-top:var(--bd) solid var(--border-soft);}
.sh-manifesto__item{display:flex;gap:var(--s-4);padding:var(--s-5) 0;border-bottom:1px solid var(--hairline);}
.sh-manifesto__num{font-family:var(--font-mono);font-size:var(--t-meta);color:var(--fg-muted);min-width:36px;padding-top:4px;}
.sh-manifesto__item-title{font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);margin-bottom:var(--s-2);letter-spacing:-0.01em;}
.sh-manifesto__item-txt{font-size:var(--t-body);color:var(--fg-soft);line-height:1.6;max-width:56ch;}
.sh-manifesto__cta{margin-top:var(--s-7);}
/* Search */
.sh-search-page{padding:var(--s-7) 0;}
.sh-search-bar{display:flex;align-items:center;gap:var(--s-2);
  border:var(--bd) solid var(--border);border-radius:var(--r-lg);
  background:var(--surface);padding:var(--s-2) var(--s-3);
  box-shadow:var(--shadow-sticker);margin-bottom:var(--s-5);}
.sh-search-input{flex:1;background:none;border:none;outline:none;
  font-family:var(--font-display);font-size:1.2rem;color:var(--fg);padding:var(--s-2);}
.sh-search-meta{margin-bottom:var(--s-5);}
.sh-search-results{display:flex;flex-direction:column;}
.sh-search-result{padding:var(--s-4) 0;border-bottom:1px solid var(--rule);cursor:pointer;}
.sh-search-result__url{font-family:var(--font-mono);font-size:12px;color:var(--clay-deep);margin-bottom:2px;display:block;}
.sh-search-result__title{font-family:var(--font-display);font-weight:700;font-size:1.15rem;
  color:var(--clay-deep);margin-bottom:4px;text-decoration:underline;
  text-decoration-color:transparent;line-height:1.3;text-wrap:pretty;}
.sh-search-result:hover .sh-search-result__title{text-decoration-color:var(--clay-deep);}
.sh-search-result__dek{font-size:var(--t-small);color:var(--fg-soft);line-height:1.55;max-width:68ch;margin-bottom:var(--s-2);}
/* Contribute */
.sh-contribute-page,.sh-nl-page{padding:var(--s-7) 0;}
.sh-page-title{font-family:var(--font-display);font-weight:800;
  font-size:var(--t-display, clamp(2.6rem,1.6rem+4.2vw,4.5rem));line-height:1.0;
  letter-spacing:-0.03em;margin:var(--s-3) 0 var(--s-4);}
.sh-page-lead{font-size:var(--t-lead);color:var(--fg-soft);line-height:1.6;max-width:50ch;margin-bottom:var(--s-6);}
.sh-steps{display:flex;flex-direction:column;gap:var(--s-3);}
.sh-step{display:flex;gap:var(--s-5);background:var(--surface);
  border:var(--bd) solid var(--border);border-radius:var(--r-lg);
  padding:var(--s-5);box-shadow:var(--shadow-sticker-sm);}
.sh-step__num{font-family:var(--font-display);font-weight:800;font-size:2.5rem;
  color:var(--lime-deep);line-height:1;min-width:54px;flex-shrink:0;}
.sh-step__title{font-family:var(--font-display);font-weight:700;font-size:var(--t-h4);margin-bottom:var(--s-2);}
.sh-step__txt{font-size:var(--t-small);color:var(--fg-soft);line-height:1.6;max-width:52ch;}
/* Quiz section */
.sh-quiz{padding:var(--s-8) 0;background:var(--surface-sunken);}
.sh-quiz__prompt{font-family:var(--font-mono);font-size:var(--t-kicker);
  color:var(--lime-deep);margin-bottom:var(--s-3);display:flex;align-items:center;gap:var(--s-2);}
[data-theme="dark"] .sh-quiz__prompt{color:var(--accent);}
.sh-quiz__title{font-family:var(--font-display);font-weight:800;
  font-size:var(--t-h2);letter-spacing:-0.02em;margin-bottom:var(--s-2);text-wrap:pretty;}
.sh-quiz__dek{font-size:var(--t-small);color:var(--fg-muted);margin-bottom:var(--s-6);}
.sh-quiz__options{display:flex;flex-direction:column;gap:var(--s-2);margin-bottom:var(--s-5);}
.sh-quiz__option{display:flex;align-items:center;gap:var(--s-3);
  padding:var(--s-3) var(--s-4);border:var(--bd) solid var(--border-soft);
  border-radius:var(--r-md);cursor:pointer;transition:transform 80ms,box-shadow 80ms,border-color 80ms;
  background:var(--surface);user-select:none;}
.sh-quiz__option:hover{border-color:var(--border);}
.sh-quiz__option.is-selected{border-color:var(--lime-deep);
  box-shadow:var(--shadow-sticker-lime);transform:translate(3px,3px);}
.sh-quiz__check-box{width:20px;height:20px;border:var(--bd) solid var(--border);
  border-radius:var(--r-xs);display:flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:800;flex-shrink:0;color:var(--lime-ink);
  background:var(--bg);transition:all 80ms;font-family:var(--font-mono);}
.sh-quiz__option.is-selected .sh-quiz__check-box{background:var(--lime);border-color:var(--lime-deep);}
.sh-quiz__option-label{font-size:var(--t-small);font-weight:500;color:var(--fg-soft);flex:1;text-wrap:pretty;}
.sh-quiz__option.is-selected .sh-quiz__option-label{color:var(--fg);}
.sh-quiz__ok{padding:var(--s-5);background:var(--surface);border:var(--bd) solid var(--lime-deep);
  border-radius:var(--r-lg);box-shadow:var(--shadow-sticker-lime);}
.sh-quiz__ok-prompt{font-family:var(--font-mono);font-size:var(--t-kicker);text-transform:uppercase;
  letter-spacing:var(--tracking-kicker);color:var(--lime-deep);display:block;margin-bottom:var(--s-2);}
.sh-quiz__ok-txt{font-size:var(--t-body);color:var(--fg-soft);line-height:var(--lh-body);}
@media(max-width:768px){.sh-quiz__option{flex-wrap:wrap;}}
/* Responsive */
@media(max-width:1024px){
  .sh-editoria-grid{grid-template-columns:repeat(3,1fr);}
  .sh-newsletter__inner{grid-template-columns:1fr;gap:var(--s-6);}
  .sh-footer__inner{grid-template-columns:1fr 1fr;gap:var(--s-5);}
  .sh-footer__brand{grid-column:span 2;}
  .sh-cards-grid{grid-template-columns:repeat(2,1fr);}
}
@media(max-width:768px){
  .sh-burger{display:flex!important;}
  .sh-nav,.sh-header__actions .sh-btn{display:none;}
  .sh-hero__grid{grid-template-columns:1fr;gap:var(--s-5);}
  .sh-hero{padding:var(--s-7) 0 var(--s-5);}
  .sh-editorial-grid{grid-template-columns:1fr;}
  .sh-card--featured,.sh-card--grid3{grid-column:span 1;}
  .sh-editorial-sidebar{grid-column:span 1;}
  .sh-editoria-grid{grid-template-columns:repeat(2,1fr);}
  .sh-cards-grid{grid-template-columns:1fr;}
  .sh-footer__inner{grid-template-columns:1fr;}
  .sh-footer__brand{grid-column:span 1;}
  .sh-manifesto__item,.sh-step{flex-direction:column;gap:var(--s-2);}
  .sh-step__num{font-size:2rem;}
  /* section-head: prompt + "ver tudo" on row 1, title full-width on row 2 */
  .sh-section-head{flex-wrap:wrap;align-items:center;row-gap:var(--s-2);}
  .sh-section-head__prompt{order:1;}
  .sh-section-head__all{order:2;margin-left:auto;}
  .sh-section-head__title{order:3;width:100%;font-size:clamp(1.55rem,5.5vw,2.2rem);}
}
      <style>{__TWEAKS_STYLE}</style>
      <div ref={dragRef} className="twk-panel" data-omelette-chrome=""
      style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
        <div className="twk-hd" onMouseDown={onDragStart}>
          <b>{title}</b>
          <button className="twk-x" aria-label="Close tweaks"
          onMouseDown={(e) => e.stopPropagation()}
          onClick={dismiss}>✕</button>
        </div>
        <div className="twk-body">
          {children}
        </div>
      </div>
    </>);

}

// ── Layout helpers ──────────────────────────────────────────────────────────

function TweakSection({ label, children }) {
  return (
    <>
      <div className="twk-sect">{label}</div>
      {children}
    </>);

}

function TweakRow({ label, value, children, inline = false }) {
  return (
    <div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>
      <div className="twk-lbl">
        <span>{label}</span>
        {value != null && <span className="twk-val">{value}</span>}
      </div>
      {children}
    </div>);

}

// ── Controls ────────────────────────────────────────────────────────────────

function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) {
  return (
    <TweakRow label={label} value={`${value}${unit}`}>
      <input type="range" className="twk-slider" min={min} max={max} step={step}
      value={value} onChange={(e) => onChange(Number(e.target.value))} />
    </TweakRow>);

}

function TweakToggle({ label, value, onChange }) {
  return (
    <div className="twk-row twk-row-h">
      <div className="twk-lbl"><span>{label}</span></div>
      <button type="button" className="twk-toggle" data-on={value ? '1' : '0'}
      role="switch" aria-checked={!!value}
      onClick={() => onChange(!value)}><i /></button>
    </div>);

}

function TweakRadio({ label, value, options, onChange }) {
  const trackRef = React.useRef(null);
  const [dragging, setDragging] = React.useState(false);
  // The active value is read by pointer-move handlers attached for the lifetime
  // of a drag — ref it so a stale closure doesn't fire onChange for every move.
  const valueRef = React.useRef(value);
  valueRef.current = value;

  // Segments wrap mid-word once per-segment width runs out. The track is
  // ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px
  // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2
  // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall
  // back to a dropdown rather than wrap.
  const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;
  const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);
  const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0);
  if (!fitsAsSegments) {
    // <select> emits strings — map back to the original option value so the
    // fallback stays type-preserving (numbers, booleans) like the segment path.
    const resolve = (s) => {
      const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s);
      return m === undefined ? s : typeof m === 'object' ? m.value : m;
    };
    return <TweakSelect label={label} value={value} options={options}
    onChange={(s) => onChange(resolve(s))} />;
  }
  const opts = options.map((o) => typeof o === 'object' ? o : { value: o, label: o });
  const idx = Math.max(0, opts.findIndex((o) => o.value === value));
  const n = opts.length;

  const segAt = (clientX) => {
    const r = trackRef.current.getBoundingClientRect();
    const inner = r.width - 4;
    const i = Math.floor((clientX - r.left - 2) / inner * n);
    return opts[Math.max(0, Math.min(n - 1, i))].value;
  };

  const onPointerDown = (e) => {
    setDragging(true);
    const v0 = segAt(e.clientX);
    if (v0 !== valueRef.current) onChange(v0);
    const move = (ev) => {
      if (!trackRef.current) return;
      const v = segAt(ev.clientX);
      if (v !== valueRef.current) onChange(v);
    };
    const up = () => {
      setDragging(false);
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };

  return (
    <TweakRow label={label}>
      <div ref={trackRef} role="radiogroup" onPointerDown={onPointerDown}
      className={dragging ? 'twk-seg dragging' : 'twk-seg'}>
        <div className="twk-seg-thumb"
        style={{ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
          width: `calc((100% - 4px) / ${n})` }} />
        {opts.map((o) =>
        <button key={o.value} type="button" role="radio" aria-checked={o.value === value}>
            {o.label}
          </button>
        )}
      </div>
    </TweakRow>);

}

function TweakSelect({ label, value, options, onChange }) {
  return (
    <TweakRow label={label}>
      <select className="twk-field" value={value} onChange={(e) => onChange(e.target.value)}>
        {options.map((o) => {
          const v = typeof o === 'object' ? o.value : o;
          const l = typeof o === 'object' ? o.label : o;
          return <option key={v} value={v}>{l}</option>;
        })}
      </select>
    </TweakRow>);

}

function TweakText({ label, value, placeholder, onChange }) {
  return (
    <TweakRow label={label}>
      <input className="twk-field" type="text" value={value} placeholder={placeholder}
      onChange={(e) => onChange(e.target.value)} />
    </TweakRow>);

}

function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) {
  const clamp = (n) => {
    if (min != null && n < min) return min;
    if (max != null && n > max) return max;
    return n;
  };
  const startRef = React.useRef({ x: 0, val: 0 });
  const onScrubStart = (e) => {
    e.preventDefault();
    startRef.current = { x: e.clientX, val: value };
    const decimals = (String(step).split('.')[1] || '').length;
    const move = (ev) => {
      const dx = ev.clientX - startRef.current.x;
      const raw = startRef.current.val + dx * step;
      const snapped = Math.round(raw / step) * step;
      onChange(clamp(Number(snapped.toFixed(decimals))));
    };
    const up = () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };
  return (
    <div className="twk-num">
      <span className="twk-num-lbl" onPointerDown={onScrubStart}>{label}</span>
      <input type="number" value={value} min={min} max={max} step={step}
      onChange={(e) => onChange(clamp(Number(e.target.value)))} />
      {unit && <span className="twk-num-unit">{unit}</span>}
    </div>);

}

// Relative-luminance contrast pick — checkmarks drawn over a swatch need to
// read on both #111 and #fafafa without per-option configuration. Hex input
// only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light".
function __twkIsLight(hex) {
  const h = String(hex).replace('#', '');
  const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0');
  const n = parseInt(x.slice(0, 6), 16);
  if (Number.isNaN(n)) return true;
  const r = n >> 16 & 255,g = n >> 8 & 255,b = n & 255;
  return r * 299 + g * 587 + b * 114 > 148000;
}

const __TwkCheck = ({ light }) =>
<svg viewBox="0 0 14 14" aria-hidden="true">
    <path d="M3 7.2 5.8 10 11 4.2" fill="none" strokeWidth="2.2"
  strokeLinecap="round" strokeLinejoin="round"
  stroke={light ? 'rgba(0,0,0,.78)' : '#fff'} />
  </svg>;


// TweakColor — curated color/palette picker. Each option is either a single
// hex string or an array of 1-5 hex strings; the card adapts — a lone color
// renders solid, a palette renders colors[0] as the hero (left ~2/3) with the
// rest stacked in a sharp column on the right. onChange emits the
// option in the shape it was passed (string stays string, array stays array).
// Without options it falls back to the native color input for back-compat.
function TweakColor({ label, value, options, onChange }) {
  if (!options || !options.length) {
    return (
      <div className="twk-row twk-row-h">
        <div className="twk-lbl"><span>{label}</span></div>
        <input type="color" className="twk-swatch" value={value}
        onChange={(e) => onChange(e.target.value)} />
      </div>);

  }
  // Native <input type=color> emits lowercase hex per the HTML spec, so
  // compare case-insensitively. String() guards JSON.stringify(undefined),
  // which returns the primitive undefined (no .toLowerCase).
  const key = (o) => String(JSON.stringify(o)).toLowerCase();
  const cur = key(value);
  return (
    <TweakRow label={label}>
      <div className="twk-chips" role="radiogroup">
        {options.map((o, i) => {
          const colors = Array.isArray(o) ? o : [o];
          const [hero, ...rest] = colors;
          const sup = rest.slice(0, 4);
          const on = key(o) === cur;
          return (
            <button key={i} type="button" className="twk-chip" role="radio"
            aria-checked={on} data-on={on ? '1' : '0'}
            aria-label={colors.join(', ')} title={colors.join(' · ')}
            style={{ background: hero }}
            onClick={() => onChange(o)}>
              {sup.length > 0 &&
              <span>
                  {sup.map((c, j) => <i key={j} style={{ background: c }} />)}
                </span>
              }
              {on && <__TwkCheck light={__twkIsLight(hero)} />}
            </button>);

        })}
      </div>
    </TweakRow>);

}

function TweakButton({ label, onClick, secondary = false }) {
  return (
    <button type="button" className={secondary ? 'twk-btn secondary' : 'twk-btn'}
    onClick={onClick}>{label}</button>);

}

Object.assign(window, {
  useTweaks, TweaksPanel, TweakSection, TweakRow,
  TweakSlider, TweakToggle, TweakRadio, TweakSelect,
  TweakText, TweakNumber, TweakColor, TweakButton
});


/* global React */
/* SERP Happens — Content Data */

const AUTHORS = {
  luiz: {
    id: 'luiz', name: 'Luiz Paulo', role: 'Coordenador de SEO', initials: 'LP',
    tint: '#C8F03F', tintText: '#44560A', city: 'São Paulo · SP',
    bio: 'Otimiza sites desde 2016, com foco em SEO técnico e Core Web Vitals. Adora um log de servidor; odeia canonical mal colocado. Acredita que perrengue documentado vale mais que case maquiado.',
    social: { linkedin: '#' }
  },
  adonai: {
    id: 'adonai', name: 'Adonai Elias', role: 'Coordenador de Conteúdo', initials: 'AE',
    tint: '#D5552C', tintText: '#fff', city: 'Belo Horizonte · MG',
    bio: 'Estrategista de conteúdo com foco em tráfego orgânico. Escreve sem apelar pra fórmula de guru. Não tem paciência pra post de 300 palavras que não responde nada de verdade.',
    social: { linkedin: '#' }
  },
  silvia: {
    id: 'silvia', name: 'Silvia Seco', role: 'Coordenadora de Inbound', initials: 'SS',
    tint: '#2F5A4C', tintText: '#fff', city: 'Santos · SP',
    bio: 'Conecta SEO, conteúdo e inbound há mais de 5 anos. Co-fundadora do SERP Happens e da Identit.ia Lab. Prefere dados a achismos e perrengue documentado a case maquiado.',
    social: { linkedin: '#', instagram: '#' }
  }
};

const EDITORIAS = [
{ slug: 'perrengues', name: 'Perrengues de SEO', tagCls: 'sh-tag--perrengues',
  accent: '#D5552C', bg: '#F3D9C8',
  dek: 'Erros, escorregadas e lições. Sem case maquiado, sem moral da história pasteurizada.', count: 14 },
{ slug: 'laboratorio', name: 'Laboratório SERP', tagCls: 'sh-tag--laboratorio',
  accent: '#2F5A4C', bg: '#d3ece6',
  dek: 'Testes e experimentos com dados reais. Se deu ruim, tem post. Se deu certo, também.', count: 9 },
{ slug: 'conteudo', name: 'Conteúdo Raiz', tagCls: 'sh-tag--conteudo',
  accent: '#5A3B6E', bg: '#e8dff2',
  dek: 'Guias completos e tutoriais. Artigo "sem volume de busca" aqui é elogio.', count: 7 },
{ slug: 'dicas', name: 'Dicas & Ferramentas', tagCls: 'sh-tag--dicas',
  accent: '#44560A', bg: '#C8F03F',
  dek: 'Scripts, planilhas e hacks da operação. Sem paywall, sem cadastro, só o arquivo.', count: 11 },
{ slug: 'carreira', name: 'Carreira sem romantização', tagCls: 'sh-tag--carreira',
  accent: '#565144', bg: '#E8E0CF',
  dek: 'Salário, burnout, agência vs. in-house. Papo real de quem tá no campo.', count: 6 }];


const POSTS = [
{ id: 1, title: 'O dia em que um robots.txt mal configurado derrubou meu tráfego em 78%',
  dek: 'Subi a alteração direto em produção, num sábado, sem staging. Era "só uma linha". O que veio depois foi uma semana de Search Console vermelho e uma conversa difícil com o cliente.',
  kind: 'Perrengues de SEO', author: 'luiz', date: '28 mai. 2026', read: '7 min',
  tone: 'clay', thumb: 'chart', featured: true },
{ id: 2, title: 'Rodei 43 variações de title tag em 30 dias. Aqui está o que realmente moveu o CTR.',
  dek: 'Metodologia, os erros no caminho, e por que a variação #31 venceu quando eu menos esperava.',
  kind: 'Laboratório SERP', author: 'adonai', date: '22 mai. 2026', read: '11 min',
  tone: 'pine', thumb: 'terminal' },
{ id: 3, title: 'Arquitetura da informação para portais de conteúdo: o guia que eu queria ter lido em 2019',
  dek: 'Do planejamento de siloed content à execução do cluster de tópicos. Com exemplos reais e erros documentados.',
  kind: 'Conteúdo Raiz', author: 'silvia', date: '15 mai. 2026', read: '18 min',
  tone: 'plum', thumb: 'diagram' },
{ id: 4, title: '5 scripts Python que uso toda semana na operação (e são todos gratuitos)',
  dek: 'Screaming Frog é caro. Mas automação não precisa ser. Aqui estão as ferramentas que salvam horas toda semana.',
  kind: 'Dicas & Ferramentas', author: 'luiz', date: '10 mai. 2026', read: '9 min',
  tone: 'lime', thumb: 'code' },
{ id: 5, title: 'Quanto ganha um analista de SEO em 2026? Dados reais, sem médias do LinkedIn.',
  dek: 'Coletamos dados de 87 profissionais com metodologia declarada. O resultado contradiz a narrativa do mercado.',
  kind: 'Carreira sem romantização', author: 'adonai', date: '5 mai. 2026', read: '12 min',
  tone: 'clay', thumb: 'chart' },
{ id: 6, title: 'Google deindexou 3 mil páginas do meu cliente em 72h — e o culpado fui eu',
  dek: 'Post-mortem completo de uma migração que parecia correta em staging e virou um incêndio em produção.',
  kind: 'Perrengues de SEO', author: 'silvia', date: '1 mai. 2026', read: '15 min',
  tone: 'ink', thumb: 'terminal' }];


Object.assign(window, { AUTHORS, POSTS, EDITORIAS });


/* global React */
/* SERP Happens — UI Components: icons, primitives, Header, Newsletter, Footer */

/* ── Icons ───────────────────────────────────────────────── */
const ICONS = {
  sun: <><circle cx="12" cy="12" r="4.2" /><path d="M12 2.5v2.4M12 19.1v2.4M4.6 4.6l1.7 1.7M17.7 17.7l1.7 1.7M2.5 12h2.4M19.1 12h2.4M4.6 19.4l1.7-1.7M17.7 6.3l1.7-1.7" /></>,
  moon: <path d="M20 14.5A8 8 0 0 1 9.5 4a6.5 6.5 0 1 0 10.5 10.5Z" />,
  menu: <path d="M4 7h16M4 12h16M4 17h16" />,
  close: <path d="M6 6l12 12M18 6 6 18" />,
  arrowLeft: <path d="M19 12H5M11 6l-6 6 6 6" />,
  arrowRight: <path d="M5 12h14M13 6l6 6-6 6" />,
  check: <><circle cx="12" cy="12" r="9.2" /><path d="M8 12.2l2.8 2.8L16 9.5" /></>,
  linkedin: <><rect x="3" y="3" width="18" height="18" rx="3" /><path d="M7 10v7M7 7v.01M11.5 17v-4a2 2 0 0 1 4 0v4M11.5 10v7" /></>,
  mail: <><rect x="3" y="5" width="18" height="14" rx="2.5" /><path d="M4 7l8 5.5L20 7" /></>,
  rss: <><path d="M5 18.5v.01" /><path d="M5 12a7 7 0 0 1 7 7M5 6a13 13 0 0 1 13 13" /></>,
  search: <><circle cx="11" cy="11" r="7" /><path d="M21 21l-5-5" /></>,
  user: <><circle cx="12" cy="8" r="4" /><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" /></>,
  terminal: <><rect x="2" y="3" width="20" height="18" rx="3" /><path d="M7 8l3 3-3 3M13 17h4" /></>
};
function Icon({ name, size = 20, stroke = 2, style }) {
  return (
    <svg viewBox="0 0 24 24" width={size} height={size} fill="none"
    stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
    style={{ display: 'block', flexShrink: 0, ...style }} aria-hidden="true">
      {ICONS[name] || null}
    </svg>);

}

/* ── Kicker ──────────────────────────────────────────────── */
function Kicker({ children, light }) {
  return <span className={`sh-kicker${light ? ' sh-kicker--light' : ''}`}>{children}</span>;
}

/* ── Tag ─────────────────────────────────────────────────── */
const TAG_MAP = {
  'Perrengues de SEO': 'sh-tag--perrengues',
  'Laboratório SERP': 'sh-tag--laboratorio',
  'Conteúdo Raiz': 'sh-tag--conteudo',
  'Dicas & Ferramentas': 'sh-tag--dicas',
  'Carreira sem romantização': 'sh-tag--carreira'
};
function Tag({ kind, children }) {
  return <span className={`sh-tag ${TAG_MAP[kind] || 'sh-tag--carreira'}`}>{children || kind}</span>;
}

/* ── Button ──────────────────────────────────────────────── */
function Button({ variant = 'primary', size, arrow, onClick, children, type }) {
  return (
    <button type={type || 'button'}
    className={`sh-btn sh-btn--${variant}${size ? ` sh-btn--${size}` : ''}`}
    onClick={onClick}>
      {children}{arrow && <Icon name="arrowRight" size={14} />}
    </button>);

}

/* ── Byline ──────────────────────────────────────────────── */
function Byline({ author, read, date, onOpenAuthor }) {
  const a = (window.AUTHORS || {})[author] || { initials: '?', name: author, tint: '#ccc', tintText: '#000' };
  return (
    <div className="sh-byline">
      <span className="sh-avatar" style={{ background: a.tint, color: a.tintText }}>{a.initials}</span>
      <span className="sh-byline__txt">
        <span className="sh-byline__name"
        style={onOpenAuthor ? { cursor: 'pointer' } : {}}
        onClick={onOpenAuthor ? (e) => {e.stopPropagation();onOpenAuthor(a.id);} : undefined}>
          {a.name}
        </span>
        {date ? ` · ${date}` : ''} · {read}
      </span>
    </div>);

}

/* ── Duotone SVG Placeholders ────────────────────────────── */
const PAL = {
  clay: { bg: '#D5552C', fg: '#F3D9C8', ac: '#1A1A15' },
  pine: { bg: '#2F5A4C', fg: '#c8ede5', ac: '#C8F03F' },
  plum: { bg: '#5A3B6E', fg: '#e4d8f5', ac: '#C8F03F' },
  lime: { bg: '#C8F03F', fg: '#1A1A15', ac: '#44560A' },
  ink: { bg: '#1A1A15', fg: '#F2ECDE', ac: '#C8F03F' }
};
function Duotone({ tone = 'clay', kind = 'chart', tall }) {
  const { bg, fg, ac } = PAL[tone] || PAL.clay;
  const bars = [[10, 74, 20, 36], [38, 52, 20, 58], [66, 30, 20, 80], [94, 58, 20, 52], [122, 40, 20, 70], [150, 62, 20, 48]];
  const dnodes = [[100, 20], [138, 42], [138, 78], [100, 100], [62, 78], [62, 42]];
  return (
    <div className={`sh-duotone${tall ? ' sh-duotone--tall' : ''}`} style={{ background: bg }}>
      <svg viewBox="0 0 200 120" preserveAspectRatio="xMidYMid slice"
      style={{ width: '100%', height: '100%', display: 'block' }}>
        <rect width="200" height="120" fill={bg} />
        <circle cx="0" cy="0" r="80" fill={fg} opacity=".04" />
        {kind === 'chart' && <>
          {bars.map(([x, y, w, h], i) =>
          <rect key={i} x={x} y={y} width={w} height={h} rx="2"
          fill={i === 2 ? fg : ac} opacity={0.7 + i * 0.03} />)}
          <polyline points="20,74 48,52 76,30 104,58 132,40 160,62"
          fill="none" stroke={fg} strokeWidth="1.5" opacity=".4" strokeDasharray="3,3" />
          <line x1="5" y1="110" x2="195" y2="110" stroke={fg} strokeWidth="1" opacity=".2" />
        </>}
        {kind === 'terminal' && <>
          <rect x="8" y="10" width="184" height="100" rx="5" fill={fg} opacity=".07"
          stroke={fg} strokeWidth=".5" strokeOpacity=".2" />
          <rect x="8" y="10" width="184" height="20" rx="5" fill={fg} opacity=".12" />
          <circle cx="22" cy="20" r="4" fill="#D5552C" opacity=".7" />
          <circle cx="33" cy="20" r="4" fill="#C8F03F" opacity=".7" />
          <circle cx="44" cy="20" r="4" fill={fg} opacity=".25" />
          <text x="14" y="46" fontFamily="monospace" fontSize="6.5" fill={ac} opacity=".9">$ python audit.py --crawl site.com</text>
          <text x="14" y="60" fontFamily="monospace" fontSize="6" fill={fg} opacity=".5">Crawling... 3.842 URLs found</text>
          <text x="14" y="73" fontFamily="monospace" fontSize="6" fill="#D5552C" opacity=".75">⚠  14 redirect chains · 87 broken links</text>
          <text x="14" y="86" fontFamily="monospace" fontSize="6" fill={fg} opacity=".35">Coverage: 96.4%  Core Web Vitals: ✓</text>
          <text x="14" y="99" fontFamily="monospace" fontSize="6.5" fill={ac} opacity=".8">$ _</text>
        </>}
        {kind === 'diagram' && <>
          {dnodes.map(([x, y], i) => <g key={i}>
            <line x1="100" y1="60" x2={x} y2={y} stroke={fg} strokeWidth=".8" opacity=".3" strokeDasharray="3,2" />
            <circle cx={x} cy={y} r="11" fill={fg} opacity=".18" stroke={fg} strokeWidth=".8" strokeOpacity=".4" />
          </g>)}
          <circle cx="100" cy="60" r="18" fill={ac} opacity=".9" />
          <text x="100" y="64" fontFamily="monospace" fontSize="6.5" fill="rgba(0,0,0,.55)"
          textAnchor="middle" fontWeight="bold">PILAR</text>
        </>}
        {kind === 'code' && <>
          <rect x="8" y="8" width="184" height="104" rx="4" fill={fg} opacity=".06"
          stroke={fg} strokeWidth=".5" strokeOpacity=".12" />
          <rect x="8" y="8" width="184" height="16" rx="4" fill={fg} opacity=".1" />
          {[34, 46, 58, 70, 82, 94, 106].map((y, i) =>
          <rect key={i} x="18" y={y - 6} width={[60, 110, 75, 45, 90, 55, 70][i]} height="7" rx="2"
          fill={i % 3 === 0 ? ac : fg} opacity={i % 3 === 0 ? .5 : .18} />)}
        </>}
      </svg>
    </div>);

}

/* ── Mascot Divider ──────────────────────────────────────── */
function MascotDivider({ variant, label, slow, hidden, staticPos }) {
  const src = variant === 'ink' ? 'assets/mascot-symbol-ink.png' : 'assets/mascot-symbol-clay.png';
  if (hidden) return <div className="sh-mascot-divider sh-mascot-divider--hidden"><div className="sh-mascot-divider__rule" /></div>;
  return (
    <div className={`sh-mascot-divider${slow ? ' sh-mascot-divider--slow' : ''}${staticPos ? ' sh-mascot-divider--static' : ''}`}>
      <div className="sh-mascot-divider__rule" />
      {label && <span className="sh-mascot-divider__label">{label}</span>}
      <div className="sh-mascot-divider__track">
        <div className="sh-mascot-divider__bob">
          <img src={src} className="sh-mascot-divider__img" alt="" width="52" height="52" loading="lazy" />
        </div>
      </div>
    </div>);

}

/* ── SERP Snippet — the innovative hero card ─────────────── */
function SerpSnippet({ onNav }) {
  return (
    <div className="sh-serp-snippet" onClick={() => onNav && onNav('perrengues')} role="button" tabIndex={0}>
      <span className="sh-serp-snippet__badge">resultado orgânico nº 1</span>
      <div className="sh-serp-snippet__header">
        <div className="sh-serp-snippet__favicon">S</div>
        <div className="sh-serp-snippet__url-wrap">
          <span className="sh-serp-snippet__domain">serphappens.com.br</span>
          <span className="sh-serp-snippet__breadcrumb"> › papo-reto-sem-guru</span>
        </div>
        <span className="sh-serp-snippet__dots">⋮</span>
      </div>
      <h3 className="sh-serp-snippet__title">SERP Happens — SEO e conteúdo sem guru nem infoproduto</h3>
      <p className="sh-serp-snippet__desc">
        Portal colaborativo e independente, feito por analistas de SEO e marketing de conteúdo do Brasil inteiro.
        Sem ferramenta, serviço ou curso pra vender.
      </p>
      <div className="sh-serp-snippet__sitelinks">
        {['Perrengues', 'Laboratório', 'Conteúdo Raiz', 'Carreira'].map((s) =>
        <span key={s} className="sh-serp-snippet__sitelink">{s} ›</span>
        )}
      </div>
    </div>);

}

/* ── Header ──────────────────────────────────────────────── */
function Header({ route, onNav, dark, onToggleDark }) {
  const [mobileOpen, setMobileOpen] = React.useState(false);
  const navItems = [
  { label: 'Perrengues', r: 'perrengues' },
  { label: 'Laboratório', r: 'laboratorio' },
  { label: 'Conteúdo Raiz', r: 'conteudo' },
  { label: 'Dicas', r: 'dicas' },
  { label: 'Carreira', r: 'carreira' }];

  return (
    <header className="sh-header">
      <div className="sh-header__bar">
        <a className="sh-logo" href="#" onClick={(e) => {e.preventDefault();onNav('home');}}>
          <img src="assets/mascot-symbol-clay.png" className="sh-logo__mark" alt="" width="32" height="32" />
          <span className="sh-logo__wm">SERP<span className="sh-logo__accent"> Happens</span></span>
        </a>
        <nav className="sh-nav">
          {navItems.map(({ label, r }) =>
          <a key={r} href="#" onClick={(e) => {e.preventDefault();onNav(r);}}
          className={`sh-nav__link${route === r ? ' is-active' : ''}`}>{label}</a>
          )}
        </nav>
        <div className="sh-header__actions">
          <button className="sh-iconbtn" onClick={() => onNav('search')} title="Buscar"><Icon name="search" size={17} /></button>
          <button className="sh-iconbtn" onClick={onToggleDark} title={dark ? 'Modo claro' : 'Modo terminal'}>
            <Icon name={dark ? 'sun' : 'terminal'} size={17} />
          </button>
          <Button variant="lime" size="sm" onClick={() => onNav('newsletter')}>Assinar a news</Button>
          <button className="sh-burger sh-iconbtn" onClick={() => setMobileOpen((o) => !o)}>
            <Icon name={mobileOpen ? 'close' : 'menu'} size={18} />
          </button>
        </div>
      </div>
      {mobileOpen &&
      <nav className="sh-mobnav">
          {navItems.map(({ label, r }) =>
        <a key={r} href="#" onClick={(e) => {e.preventDefault();onNav(r);setMobileOpen(false);}}>{label}</a>
        )}
          <a href="#" onClick={(e) => {e.preventDefault();onNav('manifesto');setMobileOpen(false);}}>Manifesto</a>
          <a href="#" onClick={(e) => {e.preventDefault();onNav('contribute');setMobileOpen(false);}}>Como contribuir</a>
        </nav>
      }
    </header>);

}

/* ── Newsletter ──────────────────────────────────────────── */
function Newsletter() {
  const [email, setEmail] = React.useState('');
  const [sent, setSent] = React.useState(false);
  return (
    <section className="sh-newsletter">
      <div className="sh-newsletter__inner">
        <div className="sh-newsletter__copy">
          <Kicker light>a news do SERP</Kicker>
          <h2 className="sh-newsletter__title">Curadoria de testes reais,<br />sem spam de guru.</h2>
          <p className="sh-newsletter__dek">Um e-mail por semana com perrengues, experimentos e achados que valeram a pena. Cancela quando quiser.</p>
        </div>
        <form className="sh-newsletter__form" onSubmit={(e) => {e.preventDefault();if (email.includes('@')) setSent(true);}}>
          {sent ?
          <div className="sh-newsletter__ok"><Icon name="check" size={20} /> SERP happens na sua caixa toda semana.</div> :
          <>
            <input className="sh-input" type="email" placeholder="voce@email.com.br"
            value={email} onChange={(e) => setEmail(e.target.value)} required />
            <Button variant="lime" arrow type="submit">Assinar</Button>
          </>}
        </form>
      </div>
    </section>);

}

/* ── Footer ──────────────────────────────────────────────── */
function Footer({ onNav }) {
  return (
    <footer className="sh-footer">
      <div className="sh-footer__inner">
        <div className="sh-footer__brand">
          <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
            <img src="assets/mascot-symbol-clay.png" style={{ height: '28px', width: 'auto' }} alt="" />
            <span style={{ fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: '1.05rem', letterSpacing: '-0.02em', color: 'var(--paper)' }}>
              SERP<span style={{ color: 'var(--lime)' }}> Happens</span>
            </span>
          </div>
          <p className="sh-footer__tag">SEO e marketing de conteúdo papo reto.<br />Sem coaches, sem gurus.</p>
          <div className="sh-footer__social">
            {[['linkedin', 'LinkedIn'], ['mail', 'Newsletter'], ['rss', 'RSS']].map(([ic, lbl]) =>
            <a key={ic} href="#" onClick={(e) => e.preventDefault()} title={lbl}><Icon name={ic} size={18} /></a>
            )}
          </div>
        </div>
        <nav className="sh-footer__col">
          <span className="sh-footer__col-head">Editorias</span>
          {['Perrengues de SEO', 'Laboratório SERP', 'Conteúdo Raiz', 'Dicas & Ferramentas', 'Carreira sem romantização'].map((e) =>
          <a key={e} href="#" onClick={(ev) => ev.preventDefault()}>{e}</a>
          )}
        </nav>
        <nav className="sh-footer__col">
          <span className="sh-footer__col-head">O portal</span>
          {[['Manifesto', 'manifesto'], ['Quem escreve', 'authors'], ['Como contribuir', 'contribute'], ['Contato', 'contact']].map(([l, r]) =>
          <a key={l} href="#" onClick={(e) => {e.preventDefault();onNav(r);}}>{l}</a>
          )}
        </nav>
      </div>
      <div className="sh-footer__legal">
        <span>© 2026 SERP Happens · feito por analistas, no Brasil</span>
        <span className="sh-footer__mono">› git push origin organic</span>
        <a href="#" onClick={(e) => {e.preventDefault();onNav('pagina-que-nao-existe');}}
           style={{ fontFamily:'var(--font-mono)',fontSize:'var(--t-meta)',color:'rgba(200,240,63,.2)',textDecoration:'none' }}
           title="Testar página 404">$ 404</a>
      </div>
    </footer>);

}

Object.assign(window, {
  Icon, Kicker, Tag, Button, Byline, Duotone,
  MascotDivider, SerpSnippet, Header, Newsletter, Footer
});


/* global React */
/* SERP Happens — Screen Components */

/* ── Post Card ───────────────────────────────────────────── */
function PostCard({ post, onOpen, compact, onOpenAuthor }) {
  return (
    <article className={`sh-card${compact ? ' sh-card--compact' : ''}`} onClick={() => onOpen(post)}>
      {!compact && <Duotone tone={post.tone} kind={post.thumb} />}
      <div className="sh-card__body">
        <Tag kind={post.kind} />
        <h3 className="sh-card__title">{post.title}</h3>
        {!compact && <p className="sh-card__dek">{post.dek}</p>}
        <Byline author={post.author} read={post.read} date={post.date} onOpenAuthor={onOpenAuthor} />
      </div>
    </article>);

}

/* ── Post Grid ───────────────────────────────────────────── */
function PostGrid({ posts, onOpen, layout, showImages, onOpenAuthor }) {
  const featured = posts[0];
  const rest = posts.slice(1);

  return (
    <section className="sh-section">
      <div className="sh-container">
        <div className="sh-section-head">
          <span className="sh-section-head__prompt">$ latest --real</span>
          <h2 className="sh-section-head__title">Últimas da operação</h2>
          <a className="sh-section-head__all" href="#" onClick={(e) => e.preventDefault()}>ver tudo →</a>
        </div>

        {layout === 'Magazine' ?
        <div className="sh-editorial-grid">
            <article className="sh-card sh-card--featured" onClick={() => onOpen(featured)}>
              {showImages !== false && <Duotone tone={featured.tone} kind={featured.thumb} tall />}
              <div className="sh-card__body">
                <Tag kind={featured.kind} />
                <h3 className="sh-card__title sh-card__title--lg">{featured.title}</h3>
                <p className="sh-card__dek">{featured.dek}</p>
                <Byline author={featured.author} read={featured.read} date={featured.date} onOpenAuthor={onOpenAuthor} />
              </div>
            </article>
            <div className="sh-editorial-sidebar">
              {rest.slice(0, 2).map((p) =>
            <article key={p.id} className="sh-card sh-card--compact" onClick={() => onOpen(p)}>
                  <div className="sh-card__body">
                    <Tag kind={p.kind} />
                    <h3 className="sh-card__title">{p.title}</h3>
                    <Byline author={p.author} read={p.read} onOpenAuthor={onOpenAuthor} />
                  </div>
                </article>
            )}
            </div>
            {rest.slice(2).map((p) =>
          <article key={p.id} className="sh-card sh-card--grid3" onClick={() => onOpen(p)}>
                {showImages !== false && <Duotone tone={p.tone} kind={p.thumb} />}
                <div className="sh-card__body">
                  <Tag kind={p.kind} />
                  <h3 className="sh-card__title">{p.title}</h3>
                  <Byline author={p.author} read={p.read} onOpenAuthor={onOpenAuthor} />
                </div>
              </article>
          )}
          </div> :
        layout === 'Lista' ?
        <div className="sh-list-grid">
            {posts.map((p) => <PostCard key={p.id} post={p} onOpen={onOpen} compact onOpenAuthor={onOpenAuthor} />)}
          </div> :

        <div className="sh-cards-grid">
            {posts.map((p) => <PostCard key={p.id} post={p} onOpen={onOpen} onOpenAuthor={onOpenAuthor} />)}
          </div>
        }
      </div>
    </section>);

}

/* ── Editoria Showcase ───────────────────────────────────── */
function EditoriaSection({ onNav }) {
  const eds = window.EDITORIAS || [];
  return (
    <section className="sh-section sh-section--muted">
      <div className="sh-container">
        <div className="sh-section-head">
          <span className="sh-section-head__prompt">$ ls editorias/</span>
          <h2 className="sh-section-head__title">As editorias</h2>
        </div>
        <div className="sh-editoria-grid">
          {eds.map((ed) =>
          <div key={ed.slug} className="sh-editoria-item" onClick={() => onNav(ed.slug)}>
              <div className="sh-editoria-item__dot" style={{ background: ed.accent }} />
              <h3 className="sh-editoria-item__name">{ed.name}</h3>
              <p className="sh-editoria-item__dek">{ed.dek}</p>
              <span className="sh-editoria-item__count">{ed.count} artigos →</span>
            </div>
          )}
        </div>
      </div>
    </section>);

}

/* ── Content Quiz ────────────────────────────────────────── */
function ContentQuiz() {
  const opts = [
    { id: 'perrengues', kind: 'Perrengues de SEO',        label: 'Mais perrengues e casos reais de produção' },
    { id: 'lab',        kind: 'Laboratório SERP',          label: 'Mais experimentos e testes com dados' },
    { id: 'guias',      kind: 'Conteúdo Raiz',             label: 'Tutoriais completos passo a passo' },
    { id: 'tools',      kind: 'Dicas & Ferramentas',       label: 'Scripts e ferramentas gratuitas da operação' },
    { id: 'carreira',   kind: 'Carreira sem romantização', label: 'Papo de carreira sem romantização' },
  ];
  const [sel, setSel] = React.useState([]);
  const [sent, setSent] = React.useState(false);
  const toggle = id => setSel(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  return (
    <section className="sh-quiz">
      <div className="sh-container sh-container--narrow">
        <div className="sh-quiz__prompt">
          <span>$</span><span>preferencias --nova-pesquisa</span>
        </div>
        <h2 className="sh-quiz__title">O que você quer ver mais por aqui?</h2>
        <p className="sh-quiz__dek">Sem cadastro, sem email. A gente usa isso pra calibrar a pauta.</p>
        {sent ? (
          <div className="sh-quiz__ok">
            <span className="sh-quiz__ok-prompt">› resposta registrada</span>
            <p className="sh-quiz__ok-txt">
              Valeu! {sel.length > 1 ? `Seus ${sel.length} temas` : 'Seu tema'} já entrou na fila da pauta.
              Se quiser saber quando sair, assina a news.
            </p>
          </div>
        ) : (
          <>
            <div className="sh-quiz__options">
              {opts.map(o => (
                <div key={o.id} className={`sh-quiz__option${sel.includes(o.id) ? ' is-selected' : ''}`}
                     onClick={() => toggle(o.id)}>
                  <span className="sh-quiz__check-box">{sel.includes(o.id) ? '✓' : ''}</span>
                  <Tag kind={o.kind}/>
                  <span className="sh-quiz__option-label">{o.label}</span>
                </div>
              ))}
            </div>
            <Button variant="lime" arrow onClick={() => sel.length > 0 && setSent(true)}>
              Enviar preferências
            </Button>
          </>
        )}
      </div>
    </section>
  );
}

/* ── Home ────────────────────────────────────────────────── */
function Home({ onNav, onOpenPost, ctx }) {
  const posts = window.POSTS || [];
  const { grid = 'Magazine', showImages = true, mascote = 'Animado' } = ctx || {};
  const mascotHidden = mascote === 'Oculto';
  const mascotStatic = mascote === 'Estático';

  return (
    <>
      <section className="sh-hero">
        <div className="sh-container">
          <div className="sh-hero__grid">
            <div className="sh-hero__left">
              <Kicker>portal independente · de analista para analista</Kicker>
              <h1 className="sh-hero__title">
                SEO e marketing de conteúdo <mark>papo reto</mark>.
              </h1>
              <p className="sh-hero__dek">
                Sem coaches, sem gurus — conteúdo técnico, pé no chão, feito por quem está na operação todo dia.
              </p>
              <div className="sh-hero__cta">
                <Button variant="primary" arrow onClick={() => onOpenPost(posts[0])}>Ler o último perrengue</Button>
                <Button variant="secondary" onClick={() => onNav('perrengues')}>Ver as editorias</Button>
              </div>
            </div>
            <div className="sh-hero__right">
              <SerpSnippet onNav={onNav} />
              <div className="sh-hero__stats">
                <span>47 artigos</span><span>·</span><span>3 autores</span><span>·</span><span>zero infoproduto</span>
              </div>
            </div>
          </div>
        </div>
      </section>

      <MascotDivider variant="clay" hidden={mascotHidden} staticPos={mascotStatic} />

      <PostGrid posts={posts} onOpen={onOpenPost} layout={grid} showImages={showImages}
      onOpenAuthor={(id) => onNav('author_' + id)} />

      <MascotDivider variant="ink" label="editorias" hidden={mascotHidden} staticPos={mascotStatic} />

      <EditoriaSection onNav={onNav} />
      <MascotDivider variant="clay" label="pauta" hidden={mascotHidden} staticPos={mascotStatic} />
      <ContentQuiz />
      <Newsletter />
    </>);

}

/* ── Callout ─────────────────────────────────────────────── */
function Callout({ title, children }) {
  return (
    <aside className="sh-callout">
      <div className="sh-callout__stripe" />
      <div className="sh-callout__inner">
        <img src="assets/mascot-symbol-clay.png" className="sh-callout__mascot" alt="" width="44" loading="lazy" />
        <div>
          <span className="sh-callout__lbl">⚠ Alerta de perrengue</span>
          <h4 className="sh-callout__title">{title || 'Esse teste deu ruim — e tá tudo bem.'}</h4>
          <p className="sh-callout__txt">{children}</p>
        </div>
      </div>
    </aside>);

}

/* ── Article ToC ─────────────────────────────────────────── */
function ArticleToc({ items }) {
  const [active, setActive] = React.useState(items[0]?.id || '');

  React.useEffect(() => {
    const onScroll = () => {
      const scrollY = window.scrollY + 110;
      let current = items[0]?.id || '';
      items.forEach(({ id }) => {
        const el = document.getElementById(id);
        if (el && el.offsetTop <= scrollY) current = id;
      });
      setActive(current);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  const scrollTo = (id) => {
    const el = document.getElementById(id);
    if (!el) return;
    const top = el.getBoundingClientRect().top + window.pageYOffset - 90;
    window.scrollTo({ top, behavior: 'smooth' });
  };

  return (
    <aside className="sh-toc">
      <div className="sh-toc__inner">
        <span className="sh-toc__head">Neste artigo</span>
        <nav className="sh-toc__nav">
          {items.map(({ id, label }) => (
            <a key={id} href={'#' + id}
               className={'sh-toc__link' + (active === id ? ' is-active' : '')}
               onClick={(e) => { e.preventDefault(); scrollTo(id); }}>
              {label}
            </a>
          ))}
        </nav>
      </div>
    </aside>
  );
}

/* ── Article View ────────────────────────────────────────── */
function ArticleView({ post, onBack, onOpenAuthor }) {
  if (!post) return null;

  const tocItems = [
    { id: 'art-o-que', label: 'O que eu mudei (e não devia)' },
    { id: 'art-como',  label: 'Como ressuscitei o site' },
    { id: 'art-moral', label: 'Moral da história' },
  ];

  return (
    <div className="sh-article-wrap">
      <div className="sh-article-back">
        <button className="sh-back" onClick={onBack}><Icon name="arrowLeft" size={14} /> voltar</button>
      </div>
      <div className="sh-article-layout">
        <article className="sh-article">
          <header className="sh-article__head">
            <Tag kind={post.kind} />
            <h1 className="sh-article__title">{post.title}</h1>
            <p className="sh-article__dek">{post.dek}</p>
            <Byline author={post.author} read={post.read} date={post.date} onOpenAuthor={onOpenAuthor} />
          </header>
          <Duotone tone={post.tone} kind={post.thumb} tall />
          <div className="sh-prose">
            <p>Vou ser direto: subi a alteração direto em produção, num sábado, sem staging. O clássico "é só uma linha". Spoiler: nunca é só uma linha.</p>
            <h2 id="art-o-que">O que eu mudei (e não devia)</h2>
            <p>Adicionei uma regra no <code>robots.txt</code> pra bloquear uma pasta de testes. O problema? O caminho tava errado e acabei mandando o Googlebot embora do site inteiro — todas as páginas, todos os produtos.</p>
            <Callout>Rodei isso em produção sem staging. Não faça igual. Abaixo, o print do Search Console no dia seguinte — e o passo a passo de como dei a volta por cima.</Callout>
            <h2 id="art-como">Como eu ressuscitei o site</h2>
            <p>Reverti a regra, forcei o recrawl das URLs prioritárias via Search Console e monitorei o índice de cobertura por duas semanas. O tráfego voltou — com um susto a menos de bagagem e um checklist a mais na operação.</p>
            <blockquote className="sh-blockquote">
              <span className="sh-blockquote__q">Artigo "sem volume de busca" aqui é elogio: significa que ajudou quem precisava, não o algoritmo.</span>
              <cite className="sh-blockquote__by">— Luiz Paulo · Coordenador de SEO</cite>
            </blockquote>
            <h2 id="art-moral">Moral da história</h2>
            <p>Tenha staging, leia o <code>robots.txt</code> duas vezes antes de subir, e documente o perrengue. Alguém — provavelmente você daqui a seis meses — vai agradecer.</p>
          </div>
          <div className="sh-tags-row">
            <Tag kind="Perrengues de SEO">SEO técnico</Tag>
            <Tag kind="Perrengues de SEO">robots.txt</Tag>
            <Tag kind="Laboratório SERP">Search Console</Tag>
          </div>
        </article>
        <ArticleToc items={tocItems} />
      </div>
    </div>);

}

/* ── Editoria Page ───────────────────────────────────────── */
function EditoriaPage({ editoria, onOpenPost, onNav, onOpenAuthor }) {
  const eds = window.EDITORIAS || [];
  const ed = eds.find((e) => e.slug === editoria) || eds[0];
  const all = window.POSTS || [];
  const filtered = all.filter((p) => {
    const k = p.kind.toLowerCase();
    return ed && (
    ed.slug === 'perrengues' && k.includes('perreng') ||
    ed.slug === 'laboratorio' && k.includes('laborat') ||
    ed.slug === 'conteudo' && k.includes('conte') ||
    ed.slug === 'dicas' && k.includes('dicas') ||
    ed.slug === 'carreira' && k.includes('carreia'));

  });
  const posts = filtered.length > 0 ? filtered : all.slice(0, 4);
  return (
    <div className="sh-editoria-page">
      <div className="sh-container">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <div className="sh-editoria-hero" style={{ '--ed-accent': ed && ed.accent }}>
          <Kicker>{ed && ed.count || 0} artigos publicados</Kicker>
          <h1 className="sh-editoria-hero__title">{ed && ed.name}</h1>
          <p className="sh-editoria-hero__dek">{ed && ed.dek}</p>
        </div>
        <div className="sh-cards-grid">
          {posts.map((p) => <PostCard key={p.id} post={p} onOpen={onOpenPost} onOpenAuthor={onOpenAuthor} />)}
        </div>
      </div>
    </div>);

}

/* ── Author Page ─────────────────────────────────────────── */
function AuthorPage({ authorId, onOpenPost, onBack, onOpenAuthor }) {
  const a = (window.AUTHORS || {})[authorId];
  if (!a) return <div className="sh-container" style={{ padding: '60px 0' }}><button className="sh-back" onClick={onBack}><Icon name="arrowLeft" size={14} /> voltar</button></div>;
  const posts = (window.POSTS || []).filter((p) => p.author === authorId);
  return (
    <div className="sh-author-page">
      <div className="sh-container">
        <button className="sh-back" onClick={onBack}><Icon name="arrowLeft" size={14} /> voltar</button>
        <div className="sh-author-card">
          <div className="sh-author-card__prompt">$ whoami</div>
          <div className="sh-author-avatar-lg" style={{ background: a.tint, color: a.tintText }}>{a.initials}</div>
          <div className="sh-author-card__name">{a.name}</div>
          <div className="sh-author-card__role">{a.role} · {a.city}</div>
          <p className="sh-author-card__bio">{a.bio}</p>
          <div className="sh-author-card__meta">{posts.length} artigos publicados</div>
        </div>
        <div className="sh-section-head" style={{ marginTop: 'var(--s-7)' }}>
          <span className="sh-section-head__prompt">$ git log --author</span>
          <h2 className="sh-section-head__title">Artigos de {a.name.split(' ')[0]}</h2>
        </div>
        <div className="sh-cards-grid">
          {(posts.length > 0 ? posts : (window.POSTS || []).slice(0, 2)).map((p) =>
          <PostCard key={p.id} post={p} onOpen={onOpenPost} onOpenAuthor={onOpenAuthor} />)}
        </div>
      </div>
    </div>);

}

/* ── Manifesto Page ──────────────────────────────────────── */
function ManifestoPage({ onNav }) {
  const items = [
  ['Transparência radical', 'Mostrar perrengue, teste que deu ruim, sem case maquiado. Se deu errado, tem post. Se deu certo, também.'],
  ['Independência', 'Sem empurrar ferramenta, serviço ou curso. O SERP Happens não vende nada além de conteúdo técnico.'],
  ['Colaboração', 'Troca respeitosa entre vários autores, de várias cidades e níveis. Ninguém é guru aqui.'],
  ['Pé no chão', 'Nada de "triplique seu tráfego em 7 dias". Foco na realidade de quem trabalha com orçamento apertado.'],
  ['Ousadia no tom', 'Pode falar "deu ruim" e até "cliente surtou", contanto que não desrespeite ninguém.'],
  ['Qualidade técnica', 'Pode ser descontraído, mas não é raso. Trabalhamos com referências, dados, prints e testes.']];

  return (
    <div className="sh-manifesto-page">
      <div className="sh-container sh-container--narrow">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <Kicker>o que acreditamos</Kicker>
        <h1 className="sh-manifesto__title">Um portal independente.<br />De analista para analista.</h1>
        <p className="sh-manifesto__lead">O SERP Happens nasceu da percepção de que não existia um lugar independente para publicar aprendizados de SEO e marketing de conteúdo sem viés de venda ou pose de guru.</p>
        <div className="sh-manifesto__items">
          {items.map(([titulo, texto], i) =>
          <div key={i} className="sh-manifesto__item">
              <span className="sh-manifesto__num">0{i + 1}</span>
              <div>
                <h3 className="sh-manifesto__item-title">{titulo}</h3>
                <p className="sh-manifesto__item-txt">{texto}</p>
              </div>
            </div>
          )}
        </div>
        <div className="sh-manifesto__cta">
          <Button variant="primary" arrow onClick={() => onNav('contribute')}>Quero contribuir</Button>
        </div>
      </div>
    </div>);

}

/* ── Search Page ─────────────────────────────────────────── */
function SearchPage({ onOpenPost, onNav }) {
  const [q, setQ] = React.useState('robots');
  const posts = window.POSTS || [];
  const results = q.length > 1 ?
  posts.filter((p) => (p.title + p.dek).toLowerCase().includes(q.toLowerCase())) :
  posts.slice(0, 4);
  return (
    <div className="sh-search-page">
      <div className="sh-container">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <div className="sh-search-bar">
          <Icon name="search" size={18} style={{ color: 'var(--fg-muted)', flexShrink: 0 }} />
          <input className="sh-search-input" type="text" placeholder="buscar artigos..."
          value={q} onChange={(e) => setQ(e.target.value)} autoFocus />
        </div>
        <div className="sh-search-meta">
          <Kicker>{results.length} resultado{results.length !== 1 ? 's' : ''} para "{q}"</Kicker>
        </div>
        <div className="sh-search-results">
          {results.map((p) =>
          <div key={p.id} className="sh-search-result" onClick={() => onOpenPost(p)}>
              <span className="sh-search-result__url">serphappens.com.br › {p.kind.toLowerCase().replace(/ /g, '-')}</span>
              <h3 className="sh-search-result__title">{p.title}</h3>
              <p className="sh-search-result__dek">{p.dek}</p>
              <div className="sh-search-result__meta"><Tag kind={p.kind} /></div>
            </div>
          )}
        </div>
      </div>
    </div>);

}

/* ── Contribute Page ─────────────────────────────────────── */
function ContributePage({ onNav }) {
  const steps = [
  ['01', 'Você tem um caso real?', 'Erro, perrengue, teste, dado, script — qualquer coisa que você usou na operação e que alguém da comunidade precisaria saber.'],
  ['02', 'Escreve sem pose de guru', 'Linguagem de operação: "rodei isso em produção", "deu ruim mas aqui está o que aprendi". Sem promessa de milagre.'],
  ['03', 'Envia pra gente revisar', 'A equipe editorial revisa com foco em precisão técnica e alinhamento de voz. É uma curadoria respeitosa, não uma triagem dura.'],
  ['04', 'Publicado com o seu nome', 'Seu artigo vai pro portal com a sua bio, cidade e link pro LinkedIn. Visibilidade sem viés de venda.']];

  return (
    <div className="sh-contribute-page">
      <div className="sh-container sh-container--narrow">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <Kicker>escreva para o SERP Happens</Kicker>
        <h1 className="sh-page-title">Como contribuir</h1>
        <p className="sh-page-lead">Você não precisa de blog próprio nem de persona de guru. Só precisa de um caso real para contar.</p>
        <div className="sh-steps">
          {steps.map(([num, title, txt]) =>
          <div key={num} className="sh-step">
              <span className="sh-step__num">{num}</span>
              <div>
                <h3 className="sh-step__title">{title}</h3>
                <p className="sh-step__txt">{txt}</p>
              </div>
            </div>
          )}
        </div>
        <div style={{ marginTop: 'var(--s-6)' }}>
          <Button variant="lime" arrow onClick={() => {}}>Enviar proposta de artigo</Button>
        </div>
      </div>
    </div>);

}

/* ── Newsletter Page ─────────────────────────────────────── */
function NewsletterPage({ onNav }) {
  const [email, setEmail] = React.useState('');
  const [sent, setSent] = React.useState(false);
  return (
    <div style={{ padding: 'var(--s-9) var(--s-5)', textAlign: 'center', maxWidth: '600px', margin: '0 auto' }}>
      <img src="assets/mascot-symbol-clay.png" alt="" width="80" height="80"
      style={{ margin: '0 auto var(--s-5)', display: 'block' }} />
      {sent ?
      <>
          <h1 className="sh-page-title">SERP happens na sua caixa!</h1>
          <p style={{ color: 'var(--fg-soft)', marginBottom: 'var(--s-5)' }}>Um e-mail por semana. Só conteúdo real, zero spam.</p>
          <Button variant="primary" onClick={() => onNav('home')}>Ver o portal</Button>
        </> :

      <>
          <Kicker>curadoria semanal</Kicker>
          <h1 className="sh-page-title">A news do SERP</h1>
          <p style={{ color: 'var(--fg-soft)', marginBottom: 'var(--s-6)', lineHeight: '1.6' }}>
            Um e-mail por semana com os melhores perrengues, experimentos e achados da operação.
          </p>
          <form style={{ display: 'flex', gap: 'var(--s-3)', justifyContent: 'center', flexWrap: 'wrap' }}
        onSubmit={(e) => {e.preventDefault();if (email.includes('@')) setSent(true);}}>
            <input className="sh-input sh-input--light" type="email" placeholder="voce@email.com.br"
          value={email} onChange={(e) => setEmail(e.target.value)} required style={{ minWidth: '260px', maxWidth: '320px' }} />
            <Button variant="lime" arrow type="submit">Assinar</Button>
          </form>
        </>
      }
    </div>);

}

/* ── Authors Directory ──────────────────────────────────── */
function AuthorsPage({ onNav, onOpenAuthor }) {
  const authors = Object.values(window.AUTHORS || {});
  const posts = window.POSTS || [];
  return (
    <div className="sh-author-page">
      <div className="sh-container">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <Kicker>de analista para analista</Kicker>
        <h1 className="sh-page-title">Quem escreve aqui</h1>
        <p className="sh-page-lead">Ninguém é guru. Todo mundo tem perrengue. Essas são as pessoas por trás do SERP Happens.</p>
        <div className="sh-cards-grid" style={{ marginTop: 'var(--s-5)' }}>
          {authors.map(a => {
            const count = posts.filter(p => p.author === a.id).length;
            return (
              <div key={a.id} className="sh-author-card" style={{ cursor: 'pointer' }}
                   onClick={() => onOpenAuthor(a.id)}>
                <div className="sh-author-card__prompt">$ whoami</div>
                <div className="sh-author-avatar-lg" style={{ background: a.tint, color: a.tintText }}>{a.initials}</div>
                <div className="sh-author-card__name">{a.name}</div>
                <div className="sh-author-card__role">{a.role} · {a.city}</div>
                <p className="sh-author-card__bio">{a.bio}</p>
                <div className="sh-author-card__meta">{count} {count === 1 ? 'artigo publicado' : 'artigos publicados'} →</div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

/* ── Contact Page ────────────────────────────────────────── */
function ContactPage({ onNav }) {
  const [form, setForm] = React.useState({ nome: '', email: '', assunto: 'colaboracao', msg: '' });
  const [sent, setSent] = React.useState(false);
  const set = (k, v) => setForm(prev => ({ ...prev, [k]: v }));
  const lbl = {
    fontFamily: 'var(--font-mono)', fontSize: 'var(--t-kicker)',
    textTransform: 'uppercase', letterSpacing: 'var(--tracking-kicker)',
    color: 'var(--fg-muted)', display: 'block', marginBottom: 'var(--s-1)'
  };
  return (
    <div className="sh-contribute-page">
      <div className="sh-container sh-container--narrow">
        <button className="sh-back" onClick={() => onNav('home')}><Icon name="arrowLeft" size={14} /> início</button>
        <Kicker>$ echo "oi" → redacao@serphappens.com.br</Kicker>
        <h1 className="sh-page-title">Tem algo pra contar?</h1>
        <p className="sh-page-lead">Colaboração, feedback, bug no portal ou só um oi — a caixa está aberta.</p>
        {sent ? (
          <div className="sh-quiz__ok">
            <span className="sh-quiz__ok-prompt">› mensagem recebida · status 200 OK</span>
            <p className="sh-quiz__ok-txt">Retornamos em até 3 dias úteis. Se for proposta de artigo, vai ver a newsletter antes — promessa de portal.</p>
            <div style={{ marginTop: 'var(--s-4)' }}><Button variant="primary" onClick={() => onNav('home')}>Ver o portal</Button></div>
          </div>
        ) : (
          <form style={{ display: 'flex', flexDirection: 'column', gap: 'var(--s-4)' }}
                onSubmit={e => { e.preventDefault(); if (form.email.includes('@') && form.msg) setSent(true); }}>
            <div style={{ display: 'flex', gap: 'var(--s-3)', flexWrap: 'wrap' }}>
              <div style={{ flex: '1 1 200px' }}>
                <label style={lbl}>Nome</label>
                <input className="sh-input sh-input--light" type="text" placeholder="Luiz Paulo"
                       value={form.nome} onChange={e => set('nome', e.target.value)} />
              </div>
              <div style={{ flex: '1 1 200px' }}>
                <label style={lbl}>E-mail *</label>
                <input className="sh-input sh-input--light" type="email" placeholder="voce@email.com.br"
                       value={form.email} onChange={e => set('email', e.target.value)} required />
              </div>
            </div>
            <div>
              <label style={lbl}>Assunto</label>
              <select className="sh-input sh-input--light" value={form.assunto}
                      onChange={e => set('assunto', e.target.value)} style={{ cursor: 'pointer' }}>
                <option value="colaboracao">Quero contribuir com um artigo</option>
                <option value="feedback">Feedback sobre o portal</option>
                <option value="bug">Encontrei um bug</option>
                <option value="parceria">Parceria ou anúncio</option>
                <option value="outro">Outro assunto</option>
              </select>
            </div>
            <div>
              <label style={lbl}>Mensagem *</label>
              <textarea className="sh-input sh-input--light" rows={5} placeholder="Conta o perrengue..."
                        value={form.msg} onChange={e => set('msg', e.target.value)} required
                        style={{ resize: 'vertical', lineHeight: '1.55' }} />
            </div>
            <div><Button variant="lime" arrow type="submit">Enviar mensagem</Button></div>
            <div style={{ borderTop: '1px solid var(--hairline)', paddingTop: 'var(--s-5)' }}>
              <span style={{ ...lbl, marginBottom: 'var(--s-3)' }}>› Outros canais</span>
              <div style={{ display: 'flex', gap: 'var(--s-3)', flexWrap: 'wrap' }}>
                {[['mail', 'redacao@serphappens.com.br'], ['linkedin', 'LinkedIn'], ['rss', 'Newsletter']].map(([ic, label]) => (
                  <a key={label} href="#" onClick={e => e.preventDefault()} className="sh-btn sh-btn--secondary sh-btn--sm">
                    <Icon name={ic} size={14} /> {label}
                  </a>
                ))}
              </div>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

/* ── 404 Page ────────────────────────────────────────────── */
function NotFoundPage({ onNav }) {
  const [blink, setBlink] = React.useState(true);
  React.useEffect(() => {
    const tid = setInterval(() => setBlink(b => !b), 530);
    return () => clearInterval(tid);
  }, []);
  return (
    <div style={{ padding: 'var(--s-9) var(--s-5) var(--s-10)', maxWidth: '680px', margin: '0 auto' }}>
      <div style={{
        background: 'var(--ink)', border: 'var(--bd) solid var(--border)',
        borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-sticker)',
        overflow: 'hidden', marginBottom: 'var(--s-7)'
      }}>
        {/* titlebar */}
        <div style={{
          background: 'rgba(255,255,255,.05)', padding: '10px 16px',
          borderBottom: '1px solid rgba(255,255,255,.08)',
          display: 'flex', alignItems: 'center', gap: '8px'
        }}>
          {['#D5552C', '#C8F03F', 'rgba(242,236,222,.15)'].map((c, i) => (
            <div key={i} style={{ width: 12, height: 12, borderRadius: '50%', background: c }} />
          ))}
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: '11px', color: 'rgba(242,236,222,.3)', marginLeft: 'auto' }}>
            search-console — cobertura — erros
          </span>
        </div>
        {/* terminal body */}
        <div style={{ padding: 'var(--s-5)', fontFamily: 'var(--font-mono)', fontSize: '13px', lineHeight: 2 }}>
          <p style={{ color: 'rgba(242,236,222,.3)' }}>$ curl -sI https://serphappens.com.br/pagina-perdida</p>
          <p>
            <span style={{ color: 'var(--lime)' }}>HTTP/2 </span>
            <span style={{ color: '#E97A50', fontWeight: 700 }}>404</span>
            <span style={{ color: 'rgba(242,236,222,.5)' }}> Not Found</span>
          </p>
          <p style={{ color: 'rgba(242,236,222,.4)' }}>x-robots-tag: noindex, nofollow</p>
          <p style={{ color: 'rgba(242,236,222,.4)' }}>server: sem-guru/2.6</p>
          <p style={{ color: 'rgba(242,236,222,.4)', marginBottom: 'var(--s-3)' }}>cache-status: MISS (óbvio)</p>
          <div style={{
            background: 'rgba(213,85,44,.12)', border: '1px solid rgba(213,85,44,.35)',
            borderRadius: 'var(--r-md)', padding: 'var(--s-3) var(--s-4)', marginBottom: 'var(--s-3)'
          }}>
            <p style={{ color: '#E97A50', fontWeight: 700, marginBottom: '4px' }}>✕ Excluído — Não encontrado (404)</p>
            <p style={{ color: 'rgba(242,236,222,.55)', fontSize: '12px', lineHeight: 1.7 }}>
              URL detectada e não encontrada. Não está no índice.<br />
              Última rastreada: nunca · Motivo: não existe
            </p>
          </div>
          <p style={{ color: 'rgba(242,236,222,.3)', fontSize: '12px' }}># Dica: verifique o sitemap antes de entrar em pânico.</p>
          <p style={{ marginTop: 'var(--s-2)' }}>
            <span style={{ color: 'var(--lime)' }}>$</span>{' '}
            <span style={{ opacity: blink ? 1 : 0, background: 'var(--lime)', color: 'var(--lime-ink)', padding: '0 1px' }}>█</span>
          </p>
        </div>
      </div>
      <Kicker>erro 404 · url não indexada</Kicker>
      <h1 style={{
        fontFamily: 'var(--font-display)', fontWeight: 800,
        fontSize: 'clamp(2rem,6vw,3.4rem)', letterSpacing: '-0.03em',
        lineHeight: 1.05, margin: 'var(--s-3) 0 var(--s-2)'
      }}>
        Essa URL não existe.<br />O Googlebot confirmou.
      </h1>
      <p style={{ color: 'var(--fg-soft)', fontSize: 'var(--t-lead)', lineHeight: 1.55, marginBottom: 'var(--s-6)', maxWidth: '44ch' }}>
        Provavelmente o link tá quebrado, foi digitado errado ou a página foi removida. Acontece — até com quem faz SEO.
      </p>
      <div style={{ display: 'flex', gap: 'var(--s-3)', flexWrap: 'wrap' }}>
        <Button variant="primary" onClick={() => onNav('home')}>Voltar ao início</Button>
        <Button variant="secondary" onClick={() => onNav('search')}>Buscar no portal</Button>
      </div>
    </div>
  );
}

Object.assign(window, {
  PostCard, PostGrid, EditoriaSection, Home,
  Callout, ArticleView, EditoriaPage, AuthorPage, AuthorsPage,
  ManifestoPage, SearchPage, ContributePage, NewsletterPage,
  ContactPage, NotFoundPage
});


/* global React, ReactDOM */
/* SERP Happens — App Router + Tweaks */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "Claro",
  "grid": "Magazine",
  "showImages": true,
  "mascote": "Animado"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRoute] = React.useState('home');
  const [activePost, setPost] = React.useState(null);
  const [activeEd, setEd] = React.useState(null);
  const [activeAuth, setAuth] = React.useState(null);

  React.useEffect(() => {
    document.documentElement.setAttribute('data-theme', t.theme === 'Terminal' ? 'dark' : 'light');
  }, [t.theme]);

  React.useEffect(() => {window.scrollTo(0, 0);}, [route]);

  const nav = React.useCallback((target) => {
    const edSlugs = ['perrengues', 'laboratorio', 'conteudo', 'dicas', 'carreira'];
    if (edSlugs.includes(target)) {setEd(target);setRoute('editoria');return;}
    if (target.startsWith('author_')) {setAuth(target.replace('author_', ''));setRoute('author');return;}
    setRoute(target);
  }, []);

  const openPost = (post) => {setPost(post);setRoute('article');};
  const openAuthor = (id) => {setAuth(id);setRoute('author');};

  const ctx = { grid: t.grid, showImages: t.showImages, mascote: t.mascote };

  return (
    <>
      <Header
        route={activeEd || route}
        onNav={nav}
        dark={t.theme === 'Terminal'}
        onToggleDark={() => setTweak('theme', t.theme === 'Terminal' ? 'Claro' : 'Terminal')} />
      
      <main id="main">
        {route === 'home'       && <Home onNav={nav} onOpenPost={openPost} ctx={ctx} />}
        {route === 'article'    && <ArticleView post={activePost} onBack={() => nav('home')} onOpenAuthor={openAuthor} />}
        {route === 'editoria'   && <EditoriaPage editoria={activeEd} onOpenPost={openPost} onNav={nav} onOpenAuthor={openAuthor} />}
        {route === 'author'     && <AuthorPage authorId={activeAuth} onOpenPost={openPost} onBack={() => nav('authors')} onOpenAuthor={openAuthor} />}
        {route === 'authors'    && <AuthorsPage onNav={nav} onOpenAuthor={openAuthor} />}
        {route === 'manifesto'  && <ManifestoPage onNav={nav} />}
        {route === 'search'     && <SearchPage onOpenPost={openPost} onNav={nav} />}
        {route === 'contribute' && <ContributePage onNav={nav} />}
        {route === 'newsletter' && <NewsletterPage onNav={nav} />}
        {route === 'contact'    && <ContactPage onNav={nav} />}
        {!['home','article','editoria','author','authors','manifesto','search','contribute','newsletter','contact'].includes(route) && <NotFoundPage onNav={nav} />}
      </main>
      <Footer onNav={nav} />

      <TweaksPanel>
        <TweakSection label="Aparência" />
        <TweakRadio label="Tema" value={t.theme}
        options={['Claro', 'Terminal']} onChange={(v) => setTweak('theme', v)} />
        <TweakSection label="Grid de posts" />
        <TweakRadio label="Layout" value={t.grid}
        options={['Magazine', 'Cards', 'Lista']} onChange={(v) => setTweak('grid', v)} />
        <TweakSection label="Cards" />
        <TweakToggle label="Com imagem" value={t.showImages} onChange={(v) => setTweak('showImages', v)} />
        <TweakSection label="Mascote" />
        <TweakSelect label="Animação" value={t.mascote}
        options={['Animado', 'Estático', 'Oculto']} onChange={(v) => setTweak('mascote', v)} />
      </TweaksPanel>
    </>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
