/* Maitha Tech — Vagas: componentes compartilhados pela família de páginas (listagem, detalhe, candidatura, indicação, confirmação). Depende de mt-primitives.jsx (Button, Accent, Reveal, Section, Eyebrow) e de mt-vagas-api.jsx. Carregado antes dos arquivos de página. */ /* —— Estilos da família (injetados uma vez, padrão de RD_STYLE) ———— */ const VAGAS_STYLE = ` .mt-vagas-orn { position: absolute; top: 0; right: -7%; width: clamp(360px, 34vw, 560px); height: auto; pointer-events: none; user-select: none; z-index: 0; opacity: 0.9; -webkit-mask-image: linear-gradient(to bottom left, #000 55%, transparent 100%); mask-image: linear-gradient(to bottom left, #000 55%, transparent 100%); } @media (max-width: 760px) { .mt-vagas-orn { width: 240px; opacity: 0.7; } } .mt-vagas-orn-detail { position: absolute; top: 0; right: -3%; width: clamp(300px, 28vw, 460px); height: auto; pointer-events: none; user-select: none; z-index: 0; opacity: 0.9; -webkit-mask-image: linear-gradient(to bottom left, #000 55%, transparent 100%); mask-image: linear-gradient(to bottom left, #000 55%, transparent 100%); } @media (max-width: 760px) { .mt-vagas-orn-detail { width: 220px; opacity: 0.7; } } .mt-vagas-orn-success { position: absolute; bottom: -70px; left: -7%; width: clamp(300px, 28vw, 460px); height: auto; pointer-events: none; user-select: none; z-index: 0; opacity: 0.9; transform: rotate(180deg); -webkit-mask-image: linear-gradient(to top right, #000 55%, transparent 100%); mask-image: linear-gradient(to top right, #000 55%, transparent 100%); } @media (max-width: 760px) { .mt-vagas-orn-success { width: 220px; opacity: 0.7; } } .mt-vagas-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; } .mt-vagas-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 22px; } .mt-vagas-span-2 { grid-column: 1 / -1; } .mt-vagas-toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .mt-vagas-dd { position: relative; } .mt-vagas-dd-panel { position: absolute; top: calc(100% + 8px); left: 0; min-width: 260px; z-index: 20; } .mt-vagas-desc p { margin: 0 0 14px; } .mt-vagas-desc p:last-child { margin-bottom: 0; } .mt-vagas-desc ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; } .mt-vagas-field select, .mt-vagas-field input { color-scheme: dark; } .mt-vagas-field input[type=checkbox], .mt-vagas-field input[type=radio] { accent-color: var(--accent); } .mt-vagas-field select option { background: var(--mt-near-black); color: var(--text-strong); } .mt-vagas-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } @keyframes mt-vagas-shimmer { from { opacity: .45; } to { opacity: .9; } } .mt-vagas-skel { background: var(--mt-graphite); border-radius: var(--radius-md); animation: mt-vagas-shimmer .9s ease-in-out infinite alternate; } @media (prefers-reduced-motion: reduce) { .mt-vagas-skel { animation: none; } } @media (max-width: 760px) { .mt-vagas-grid, .mt-vagas-form-grid { grid-template-columns: 1fr !important; } .mt-vagas-dd-panel { position: static; min-width: 0; margin-top: 8px; } } `; function vagasInjectStyle() { if (document.getElementById("mt-vagas-style")) return; const el = document.createElement("style"); el.id = "mt-vagas-style"; el.textContent = VAGAS_STYLE; document.head.appendChild(el); } vagasInjectStyle(); /* —— Ícones (paths Lucide, traço currentColor) ————————————— */ const VAGAS_ICON_PATHS = { "map-pin": '', briefcase: '', layers: '', tag: '', monitor: '', search: '', "chevron-down": '', "chevron-left": '', "chevron-right": '', "arrow-left": '', "arrow-right": '', check: '', plus: '', paperclip: '', "circle-check": '', alert: '', x: '', house: '', linkedin: '', instagram: '', youtube: '', }; function VagasIcon({ name, size = 16, stroke = 1.75, style = {}, className = "" }) { const inner = VAGAS_ICON_PATHS[name] || ""; return ( ); } /* —— Hero compacto (sem imagem) ——————————————————————————— */ function VagasBackLink({ href, children }) { return ( {children} ); } function VagasHero({ eyebrow, title, lead, back, size = "lg", children }) { const fs = size === "lg" ? "clamp(2.6rem, 5.4vw, 4.8rem)" : "clamp(2rem, 3.6vw, 3rem)"; return (
{back ? {back.label} : null} {eyebrow ? ( {eyebrow} ) : null}

{title}

{lead ?

{lead}

: null} {children}
); } /* —— Tags / meta —————————————————————————————————————— */ function VagasTag({ icon, children, tone = "neutral" }) { return ( {icon ? : null} {children} ); } /* Localização + senioridade (+ modelo de trabalho no detalhe). O regime de contratação (hiringType) não é exibido por decisão de produto. */ function VagasMetaRow({ location, profile, model, tone = "warm", style = {} }) { const loc = vagasNormalizeLocation(location).label; return (
{loc ? {loc} : null} {model ? {model} : null} {profile ? {profile} : null}
); } /* —— Card de vaga ————————————————————————————————————— */ function VagasJobCard({ job, delay = 0 }) { const [h, setH] = React.useState(false); const skills = (job.skills || []).slice(0, 3).join(" · "); return ( setH(true)} onMouseLeave={() => setH(false)} onFocus={() => setH(true)} onBlur={() => setH(false)} style={{ display: "flex", flexDirection: "column", gap: 16, height: "100%", boxSizing: "border-box", background: "var(--surface-card)", border: `1px solid ${h ? "var(--accent)" : "var(--border-default)"}`, borderRadius: "var(--radius-lg)", padding: "clamp(22px, 2.6vw, 30px)", color: "inherit", textDecoration: "none", transform: h ? "translateY(-4px)" : "none", transition: "border-color var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out)" }}>
{skills ? {skills} : null}

{job.name}

Ver vaga
); } /* —— Busca ————————————————————————————————————————— */ function VagasSearchBar({ value, onChange, count, placeholder = "Buscar por vagas" }) { const [focus, setFocus] = React.useState(false); return (
onChange(e.target.value)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} type="search" placeholder={placeholder} aria-label="Buscar vagas" style={{ flex: 1, minWidth: 0, padding: "0.8rem 0", fontFamily: "var(--font-sans)", fontSize: "var(--fs-body)", color: "var(--text-strong)", background: "transparent", border: 0, outline: "none" }} /> {typeof count === "number" ? {count} {count === 1 ? "vaga" : "vagas"} : null}
); } /* —— Chip de filtro com dropdown de checkboxes ———————————— */ function VagasFilterChip({ label, icon, options, selected, onToggle, onClear }) { const [open, setOpen] = React.useState(false); const [h, setH] = React.useState(false); const ref = React.useRef(null); const n = selected.size; React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDoc); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); }; }, [open]); const active = n > 0; return (
{open ? (
{options.length === 0 ?
Sem opções
: null} {options.map((o) => { const on = selected.has(o.key); return ( ); })} {n > 0 && onClear ? ( ) : null}
) : null}
); } /* —— Paginação ———————————————————————————————————— */ function VagasPageButton({ children, active, disabled, onClick, label }) { const [h, setH] = React.useState(false); return ( ); } function vagasPageList(page, total) { if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1); const set = new Set([1, total, page - 1, page, page + 1].filter((p) => p >= 1 && p <= total)); const arr = Array.from(set).sort((a, b) => a - b); const out = []; arr.forEach((p, i) => { if (i && p - arr[i - 1] > 1) out.push("…"); out.push(p); }); return out; } function VagasPagination({ page, pageSize, total, onPage }) { const pages = Math.max(1, Math.ceil(total / pageSize)); if (total <= pageSize) return null; const shown = Math.min(total, page * pageSize) - (page - 1) * pageSize; return ( ); } /* —— Controles de formulário ————————————————————————— */ function vagasControlStyle(focused, error, extra = {}) { return { width: "100%", boxSizing: "border-box", minHeight: "3rem", padding: "0.75rem 1rem", fontFamily: "var(--font-sans)", fontSize: "var(--fs-body)", lineHeight: 1.5, color: "var(--text-strong)", background: "var(--mt-near-black)", borderRadius: "var(--radius-md)", outline: "none", border: `1px solid ${error ? "var(--mt-danger)" : focused ? "var(--mt-white)" : "var(--border-strong)"}`, boxShadow: focused ? "0 0 0 4px var(--focus-ring)" : "none", transition: "border-color var(--dur-base), box-shadow var(--dur-base)", ...extra, }; } function VagasField({ label, required, error, hint, htmlFor, span2 = false, children }) { return (
{label ? ( ) : null} {children} {error ? {error} : hint ? {hint} : null}
); } function VagasInput({ error, style = {}, ...rest }) { const [focus, setFocus] = React.useState(false); return ( { setFocus(true); rest.onFocus && rest.onFocus(e); }} onBlur={(e) => { setFocus(false); rest.onBlur && rest.onBlur(e); }} aria-invalid={error ? "true" : undefined} style={vagasControlStyle(focus, error, style)} /> ); } /* options: [{value,label}] ou strings */ function VagasSelect({ options = [], placeholder = "Selecione", value, onChange, error, loading = false, disabled = false, id, ...rest }) { const [focus, setFocus] = React.useState(false); const seen = new Set(); const opts = options.map((o) => (typeof o === "string" ? { value: o, label: o } : o)).filter((o) => { if (seen.has(o.value)) return false; seen.add(o.value); return true; }); return (
); } const VAGAS_CV_ACCEPT = ".pdf,.docx,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document"; function VagasFileInput({ id, file, onChange, error, accept = VAGAS_CV_ACCEPT }) { const [focus, setFocus] = React.useState(false); const [over, setOver] = React.useState(false); const lit = focus || over; return (
onChange(e.target.files && e.target.files[0] ? e.target.files[0] : null)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} />
); } /* options: [{value,label}] — pills exclusivas */ function VagasRadioGroup({ name, options, value, onChange }) { return (
{options.map((o) => { const on = value === o.value; return ( ); })}
); } function VagasCheckbox({ id, checked, onChange, error, children }) { return ( ); } function VagasSubmitBar({ loading, label = "Enviar", error }) { return (
{error ? (
{error}
) : null}
); } /* —— Descrição da vaga ————————————————————————————— */ function VagasBullet({ kind = "check", children }) { const box = { flex: "none", marginTop: 2, width: 22, height: 22, borderRadius: "50%", display: "inline-flex", alignItems: "center", justifyContent: "center" }; let mark; if (kind === "check") mark = ; else if (kind === "plus") mark = ; else mark = ; return (
  • {mark} {children}
  • ); } function VagasDetailSection({ title, blocks, delay = 0 }) { return ( {title ?

    {title}

    : null} {blocks.map((b, i) => b.type === "ul" ?
      {b.items.map((it, j) => {it})}
    :

    {b.text}

    )}
    ); } function VagasDescription({ text }) { const sections = React.useMemo(() => vagasParseDescription(text), [text]); return (
    {sections.map((s, i) => )}
    ); } /* —— Estados ————————————————————————————————————— */ function VagasSuccessPanel({ title, children, cta, extra }) { return (

    {title}

    {children ?

    {children}

    : null} {cta ?
    {cta}
    : null} {extra ?
    {extra}
    : null}
    ); } function VagasSkeleton({ variant = "cards", count = 6 }) { if (variant === "cards") { return (
    {Array.from({ length: count }, (_, i) => (
    ))}
    ); } if (variant === "form") { return (
    {Array.from({ length: count }, (_, i) =>
    )}
    ); } return (
    ); } function VagasEmptyState({ query, onClear }) { return (

    Nenhuma vaga encontrada{query ? ` para “${query}”` : ""}.

    {onClear ?
    : null}
    ); } function VagasErrorState({ title = "Não foi possível carregar", message, onRetry, cta }) { return (

    {title}

    {message ?

    {message}

    : null}
    {onRetry ? : null} {cta}
    ); } Object.assign(window, { VAGAS_STYLE, vagasInjectStyle, VAGAS_ICON_PATHS, VagasIcon, VAGAS_CV_ACCEPT, VagasBackLink, VagasHero, VagasTag, VagasMetaRow, VagasJobCard, VagasSearchBar, VagasFilterChip, VagasPagination, vagasControlStyle, VagasField, VagasInput, VagasSelect, VagasFileInput, VagasRadioGroup, VagasCheckbox, VagasSubmitBar, VagasBullet, VagasDetailSection, VagasDescription, VagasSuccessPanel, VagasSkeleton, VagasEmptyState, VagasErrorState, });