// Just Travel — "Trabalhe conosco" (careers) page app. Tag: CAR
// Loaded as <script type="text/babel"> AFTER React UMD, landing-sections.jsx,
// landing-footer.jsx, jobs-data.js and image-slot.js. Shares ONE global scope
// with the other babel scripts, so:
//   - no import/export/require
//   - no top-level `const { useState } = React` (would collide) — use React.* directly
//   - every top-level name is unique and prefixed CAR_ / CARApp
// This page does not list openings: Vagas.html is the one listing. What it does
// read from InHire is the same live feed, for the hero search, which answers as
// the visitor types with the openings that are actually open. No vacancy is
// written in this file: no titles, no locations, no counts.

/* jobs-data.js defines window.InHire. If it did not load, or an older copy of
   it did, referencing the global directly throws while this module is being
   evaluated and nothing renders: the whole page goes blank over one missing
   script. This stand-in keeps the page up, and it carries the talent bank URL
   itself so the button still works in exactly the case the stand-in exists
   for. */
const CAR_IH = window.InHire || {
  SITE: "https://justtravel.inhire.app",
  TALENT_POOL:
    "https://justtravel.inhire.app/vagas/6ca61330-4c82-4579-811f-9a4e5ba5b377/banco-de-talentos-or-todas-as-areas-or-just-travel",
  useJobs: () => ({ status: "error", jobs: [], error: "o módulo de vagas não carregou" }),
  matchQuery: () => false,
  highlight: (text) => [{ text: String(text == null ? "" : text), hit: false }],
};

/* Culture carousel. Photographs of the team in the Salvador office, which
   replaced the stock pictures this list used to carry: the section claims a
   culture, so the faces in it should be the people who work here. They are
   deliberately uncaptioned, because a caption is a claim about who is in the
   frame. To swap or add one, put the original in media-src/photos/cultura/,
   add a line to PHOTOS in scripts/culture-photos.mjs, run it, and list the
   slug here. */
const CAR_CULTURE_PHOTOS = [
  "assets/photos/cultura/tela.avif",
  "assets/photos/cultura/comemoracao.avif",
  "assets/photos/cultura/abraco.avif",
  "assets/photos/cultura/copa.avif",
  "assets/photos/cultura/lounge.avif",
];

/* One photo every 4s, the same shape as the catalogue rotation on the landing
   (PRODUCT_ROTATE_MS in landing-sections.jsx): the timer is per shown photo
   rather than one interval for the carousel, so a click restarts the full dwell
   instead of inheriting whatever was left of the tick it interrupted. Hover or
   focus holds it, reduced motion never starts it, and the first arrow or dot
   hands the carousel over for good, because someone picking through the photos
   should not have one slide out from under them. */
const CAR_PHOTO_MS = 4000;

/* Missão e visão, transcritas do guia institucional da empresa. As duas frases
   são citações: mexer na redação aqui muda o que a companhia diz de si mesma,
   então elas só mudam quando o guia muda. O prazo da visão ("nos próximos 3
   anos") é do texto original e foi mantido como está por isso mesmo; se o guia
   passar a fixar um ano, é aqui que ele entra. */
const CAR_GUIDE = {
  missao:
    "Proporcionar de forma inovadora a possibilidade de um agente do turismo competir com as principais agências de turismo online, objetivando uma transformação digital no modelo de negócio.",
  visao:
    "Ser a maior e melhor plataforma B2B de conexão de produtos turísticos para agentes do turismo na América Latina nos próximos 3 anos.",
};

/* Os seis valores do guia institucional: Respeito, Colaboração, Ética
   empresarial, Diversidade, Transparência e Inovação. Os nomes são os do guia,
   nesta ordem, e substituíram cinco valores que a página tinha escrito por
   conta própria. A linha sob cada nome é da página, não do guia: ela explica o
   valor no vocabulário da missão (o agente competindo com as grandes agências
   online) e pode ser reescrita; os nomes, não. Se o guia ganhar ou perder um
   valor, ajuste também as colunas de `.car-values__list` em
   "Trabalhe Conosco.html", que hoje contam com seis. */
const CAR_VALUES = [
  { n: "01", name: "Respeito", txt: "Vale para o colega, para a agência parceira e para o viajante do outro lado. É o começo de toda conversa aqui." },
  { n: "02", name: "Colaboração", txt: "Nenhuma área entrega sozinha: o resultado da agência parceira passa por todas elas." },
  { n: "03", name: "Ética empresarial", txt: "O certo mesmo quando ninguém está olhando, com o time, com o parceiro e com o mercado." },
  { n: "04", name: "Diversidade", txt: "Gente de perfis, regiões e histórias diferentes decide melhor para um turismo que é de todos." },
  { n: "05", name: "Transparência", txt: "Contexto aberto, feedback direto e metas visíveis para todo mundo." },
  { n: "06", name: "Inovação", txt: "Tecnologia para o agente de viagens competir de igual para igual com as maiores agências online." },
];

/* The areas the company is organised into, as the ten names the team uses
   internally. They carry no description on purpose: a line under each one would
   be a claim about what that team does and who it reports to, which is not ours
   to invent. The glyph is the only gloss, and each key is used once here so no
   two areas read as the same thing.

   Photography. Two areas have a photograph of their own, from
   scripts/area-photos.mjs. The other eight reuse the culture carousel's files,
   which cost nothing: the carousel higher up the page has already downloaded
   them, so these tiles paint from cache.
   Reusing them means five photographs cover eight tiles, and the pairs are
   dealt by hand rather than cycled so that no tile sits next to, or directly
   above, its own twin. The five-column desktop grid and the two-column tablet
   one both hold; read the columns of this list five at a time to check the
   first, two at a time for the second.
   A photograph here says "this is the company", not "this is that team": the
   frames are of the Salvador office and the areas they are paired with are the
   ones they did not already belong to. That is also why they are decorative,
   alt="", exactly like the carousel. */
const CAR_CULTURE_DIR = "assets/photos/cultura/";
const CAR_AREAS = [
  { icon: "code", name: "Tecnologia", photo: "assets/photos/carreiras/tecnologia.avif" },
  { icon: "palette", name: "Marketing", photo: CAR_CULTURE_DIR + "comemoracao.avif" },
  { icon: "monitor", name: "Produtos", photo: "assets/photos/carreiras/produtos.avif" },
  { icon: "chart", name: "Controle e Inteligência", photo: CAR_CULTURE_DIR + "abraco.avif" },
  { icon: "home", name: "Facilities", photo: CAR_CULTURE_DIR + "copa.avif" },
  { icon: "funnel", name: "Comercial", photo: CAR_CULTURE_DIR + "tela.avif" },
  { icon: "settings", name: "Operacional", photo: CAR_CULTURE_DIR + "lounge.avif" },
  { icon: "wallet", name: "Administrativo Financeiro", photo: CAR_CULTURE_DIR + "comemoracao.avif" },
  { icon: "pin", name: "Projetos Just Cidades", photo: CAR_CULTURE_DIR + "copa.avif" },
  { icon: "users", name: "Gente & Gestão (G&G)", photo: CAR_CULTURE_DIR + "abraco.avif" },
];

const CAR_BENEFITS = [
  { icon: "rocket", t: "Plano de carreira", d: "Trilhas claras de evolução com avaliações e promoções a cada ciclo." },
  { icon: "clock", t: "Horário flexível", d: "Foque no resultado, não no relógio. Você organiza o seu dia." },
  { icon: "shield", t: "Saúde e bem-estar", d: "Plano de saúde e odontológico, além de apoio psicológico." },
  { icon: "chart", t: "Participação nos resultados", d: "Quando a Just cresce, o time cresce junto via PLR." },
  { icon: "users", t: "Time diverso", d: "Gente de perfis, regiões e histórias diferentes construindo juntas." },
  { icon: "sparkles", t: "Day off de aniversário", d: "Seu dia é seu. Comemore longe do computador." },
  { icon: "book", t: "Auxílio educação", d: "Cursos, idiomas e a Just Academy para você nunca parar de aprender." },
  { icon: "chat", t: "Cultura de feedback", d: "Conversas frequentes, 1:1s e feedback honesto para você crescer." },
];

/* The channel stickers still take their profile URLs from SOCIALS in
   landing-footer.jsx, so this page and the footer never drift apart on where
   they send people. (They used to be four href="#" placeholders, which only
   jumped to the top.) The artwork is local because it is a different job here:
   the footer glyphs are monochrome and inherit the footer ink, while these are
   each network's own brand mark, a full bleed circle in the brand colour with
   the glyph in white. Every one is drawn in the same 48x48 box so they come out
   the same size, optically centred, whatever the glyph inside is doing.

   A network with no art here renders nothing, so a channel added to SOCIALS
   without its sticker would leave an empty 96px hole: CAR_CHANNELS drops what
   it cannot draw and the footer still carries the link. */
const CAR_CHANNEL_ART = {
  Instagram: (
    <>
      <defs>
        {/* The Instagram gradient runs from the warm corner outwards, so it is
            radial and anchored past the bottom left edge, not a diagonal ramp. */}
        <radialGradient id="car-ch-ig" cx="0.28" cy="1.02" r="1.25">
          <stop offset="0" stopColor="#FFD879" />
          <stop offset="0.18" stopColor="#FDB338" />
          <stop offset="0.38" stopColor="#F8703C" />
          <stop offset="0.58" stopColor="#E8256F" />
          <stop offset="0.8" stopColor="#A32DB4" />
          <stop offset="1" stopColor="#4B57D8" />
        </radialGradient>
      </defs>
      <circle cx="24" cy="24" r="24" fill="url(#car-ch-ig)" />
      <g fill="none" stroke="#fff" strokeWidth="3" strokeLinejoin="round">
        <rect x="12.5" y="12.5" width="23" height="23" rx="7" />
        <circle cx="24" cy="24" r="6.2" />
      </g>
      <circle cx="30.4" cy="17.6" r="1.75" fill="#fff" />
    </>
  ),
  LinkedIn: (
    <>
      <circle cx="24" cy="24" r="24" fill="#0A66C2" />
      {/* The "in" only, white on the brand blue: the mark that ships inside a
          rounded square would knock the letters back out to blue here. */}
      <g fill="#fff" transform="translate(10.3 12.2) scale(1.05)">
        <circle cx="4.2" cy="4.2" r="2.7" />
        <path d="M1.6 8.4h5.2v12.4H1.6z" />
        <path d="M10.2 20.8V8.4h5v2c.9-1.4 2.5-2.3 4.4-2.3 3 0 4.8 2 4.8 5.5v7.2h-5.2v-6.4c0-1.6-.7-2.6-2-2.6s-2 1-2 2.6v6.4z" />
      </g>
    </>
  ),
  YouTube: (
    <>
      <circle cx="24" cy="24" r="24" fill="#FF0000" />
      <rect x="13" y="16.3" width="22" height="15.4" rx="4.6" fill="#fff" />
      {/* The play triangle is the circle's red showing through the white body. */}
      <path d="M21.7 20.4 28.4 24l-6.7 3.6z" fill="#FF0000" />
    </>
  ),
  WhatsApp: (
    <>
      <defs>
        <linearGradient id="car-ch-wa" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="#5BD066" />
          <stop offset="1" stopColor="#27A342" />
        </linearGradient>
      </defs>
      <circle cx="24" cy="24" r="24" fill="url(#car-ch-wa)" />
      <g fill="#fff" transform="translate(9.6 9.6) scale(1.2)">
        <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.964-.966 1.162-.199.198-.397.223-.694.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.347-.446.52-.644.174-.198.223-.34.335-.588.112-.248.056-.421-.056-.594-.111-.173-.669-1.611-.916-2.198-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.263.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413" />
      </g>
    </>
  ),
  /* TikTok publishes its mark as one glyph in three colours: the note in white
     with a cyan and a magenta copy of itself offset behind it. So it is drawn
     three times from the registry path (IC_SHARP.tiktok, simple-icons) instead
     of being retyped here, which keeps the geometry in the one file that owns
     it. The two offsets are equal and opposite, so the stack stays centred in
     the 48 box like the others. Black is the mark's own ground, not a plate:
     the drop shadow is what lifts it off the blue band. */
  TikTok: (
    <>
      <circle cx="24" cy="24" r="24" fill="#010101" />
      <g transform="translate(11.3 11.25) scale(1.06)">
        <path d={IC_SHARP.tiktok[0]} fill="#25F4EE" transform="translate(-1 1)" />
        <path d={IC_SHARP.tiktok[0]} fill="#FE2C55" transform="translate(1 -1)" />
        <path d={IC_SHARP.tiktok[0]} fill="#fff" />
      </g>
    </>
  ),
};

const CAR_CHANNELS = SOCIALS
  .map((s) => ({ ...s, art: CAR_CHANNEL_ART[s.label] }))
  .filter((s) => s.art);

/* Submitting the search goes to Vagas.html, the one listing, carrying what was
   typed rather than dropping it: that page reads ?q= on mount. This is what
   happens when no row of the dropdown was picked. */
function CAR_searchUrl(query) {
  const q = (query || "").trim();
  return q ? `Vagas.html?q=${encodeURIComponent(q)}` : "Vagas.html";
}

/* ------------------------------------------------- hero search autocomplete

   The hero box used to be a form and nothing else: it took a term and handed it
   to Vagas.html, so the visitor typed into a field that could not tell them
   whether the role they were after exists. It now answers on every keystroke
   from the InHire feed, matched on title, city and work model by the shared
   matcher in jobs-data.js, the same one the listing filters with.

   Only what the ATS returns as published reaches this list, so the dropdown
   cannot offer an opening that is not open, and a failed fetch says so instead
   of falling back to anything.

   The panel is capped at six rows: it is a shortcut, not the listing, and the
   button under the rows is the way to the whole of it. */
const CAR_SUGGEST_MAX = 6;

/* ------------------------------------------------------------ hero typewriter

   The h1 types itself. The lap is the question, then five roles, then the
   question again, and the roles are held to five on purpose: the lap has to
   come back around while the visitor is still looking at the hero.

   Every role belongs to an area that has a card in CAR_AREAS below (Produtos,
   Controle e Inteligência, Tecnologia, Marketing), so the line the hero types
   and the grid the visitor scrolls into are the same company. A role whose area
   is not on that grid does not belong here.

   These are job families, not openings: nothing here claims a vacancy is open,
   which is what the InHire feed answers for. */
const CAR_TYPE_LEAD = "Qual é o seu próximo destino?";
const CAR_TYPE_ROLES = [
  "Product Designer",
  "Analista de Dados",
  "Engenheiro(a) de Software",
  "Analista de Produtos",
  "Analista de Marketing",
];
const CAR_TYPE_LINES = [CAR_TYPE_LEAD, ...CAR_TYPE_ROLES];

/* Per-character beats. Typing is slower than erasing because that is how a
   typewriter reads: writing is deliberate, taking it back is a swipe. The
   question is held nearly twice as long as a role, since it is the line the
   page is actually asking, and the beat is the empty pause after a line is
   gone, which keeps the two words from touching. */
const CAR_TYPE_MS = 62;
const CAR_TYPE_ERASE_MS = 32;
const CAR_TYPE_HOLD_LEAD_MS = 2200;
const CAR_TYPE_HOLD_MS = 1400;
const CAR_TYPE_BEAT_MS = 420;

function CAR_Typewriter() {
  const still = prefersReducedMotion();
  /* One timeout per frame, not one interval: each phase sets its own delay, so
     the rhythm changes without a second timer to keep in step. */
  const [step, setStep] = React.useState({ i: 0, n: 0, phase: "typing" });
  const { i, n, phase } = step;

  React.useEffect(() => {
    if (still) return;
    const line = CAR_TYPE_LINES[i];
    let ms, next;
    if (phase === "typing") {
      if (n < line.length) { ms = CAR_TYPE_MS; next = { i, n: n + 1, phase }; }
      // Zero delay: the hold is its own phase only so the caret can blink
      // through it and stay solid while the letters move.
      else { ms = 0; next = { i, n, phase: "holding" }; }
    } else if (phase === "holding") {
      ms = i === 0 ? CAR_TYPE_HOLD_LEAD_MS : CAR_TYPE_HOLD_MS;
      next = { i, n, phase: "erasing" };
    } else if (n > 0) {
      ms = CAR_TYPE_ERASE_MS; next = { i, n: n - 1, phase };
    } else {
      ms = CAR_TYPE_BEAT_MS;
      next = { i: (i + 1) % CAR_TYPE_LINES.length, n: 0, phase: "typing" };
    }
    const id = setTimeout(() => setStep(next), ms);
    return () => clearTimeout(id);
  }, [i, n, phase, still]);

  const shown = still ? CAR_TYPE_LEAD : CAR_TYPE_LINES[i].slice(0, n);

  return (
    <span className="car-type">
      {/* Sizer: the longest line of the lap, rendered invisibly to hold the
          block at its tallest, so the subtitle and the search field below never
          move as the words change length. It carries a caret of its own, unseen
          like the rest of it, because the live copy always ends in one: without
          it the two measure differently and the question wraps a line earlier
          than the space reserved for it. */}
      <span className="car-type__sizer" aria-hidden="true">
        {CAR_TYPE_LEAD}
        <span className="car-type__caret"></span>
      </span>
      <span className="car-type__live" aria-hidden="true">
        {shown}
        <span className={"car-type__caret" + (phase === "holding" || still ? " is-blinking" : "")}></span>
      </span>
      {/* The question is the page's heading, so it stays whole in the
          accessibility tree: a screen reader reads it once instead of being
          handed a new h1 every 62ms. */}
      <span className="sr-only">{CAR_TYPE_LEAD}</span>
    </span>
  );
}

/* One field of a row, with every stretch the query matched painted blue.
   Nothing is assembled as markup: highlight() hands back plain-text runs and
   each one becomes its own node, so what was typed never goes near innerHTML. */
function CAR_Mark({ text, query }) {
  return (
    <>
      {CAR_IH.highlight(text, query).map((run, i) => (
        run.hit
          ? <mark className="car-sg__hit" key={i}>{run.text}</mark>
          : <React.Fragment key={i}>{run.text}</React.Fragment>
      ))}
    </>
  );
}

/* A row is a real link to the opening in InHire, which is where an application
   is made: href, target and all, so it can be middle-clicked or opened in a new
   tab like any other link. The combobox roles sit on top of that. */
function CAR_SuggestRow({ job, query, active, id, onHover, onPick }) {
  /* City and work model, in that order, minus the repetition: a remote opening
     is often filed in InHire with "Remoto" as its location too, and the row
     would read "Remoto · Remoto". */
  const meta = [job.location, job.workplace]
    .filter(Boolean)
    .filter((v, i, all) => all.indexOf(v) === i);

  return (
    <a
      id={id}
      role="option"
      aria-selected={active}
      className={`car-sg__item${active ? " is-active" : ""}`}
      href={job.url}
      target="_blank"
      rel="noopener noreferrer"
      onMouseMove={onHover}
      onClick={onPick}>
      <span className="car-sg__main">
        <span className="car-sg__title"><CAR_Mark text={job.title} query={query} /></span>
        <span className="car-sg__meta">
          {meta.map((field, i) => (
            <React.Fragment key={field}>
              {i > 0 && <span className="car-sg__dot" aria-hidden="true">·</span>}
              <span><CAR_Mark text={field} query={query} /></span>
            </React.Fragment>
          ))}
        </span>
      </span>
      <span className="car-sg__go" aria-hidden="true">
        <SIcon name="arrowRight" size={18} />
      </span>
    </a>
  );
}

/* ------------------------------------------------------------------ hero

   Every path out of the hero lands on a real opening: a row goes straight to
   that vacancy in InHire, and everything else (the button, Enter with no row
   picked, the ghost link) goes to Vagas.html, which lists all of them. */
function CAR_Hero({ query, setQuery, feed }) {
  const [open, setOpen] = React.useState(false);
  // -1 is "no row picked", the state the panel opens in, so Enter submits the
  // search instead of opening whichever opening happens to be first.
  const [active, setActive] = React.useState(-1);
  const wrapRef = React.useRef(null);
  const listRef = React.useRef(null);

  const jobs = feed.status === "ready" ? feed.jobs : [];
  const hits = jobs.filter((j) => CAR_IH.matchQuery(j, query));
  const shown = hits.slice(0, CAR_SUGGEST_MAX);
  const typed = query.trim().length > 0;

  // Each keystroke rebuilds the list under the selection, so the selection
  // goes: keeping the index would slide it onto an unrelated opening.
  React.useEffect(() => { setActive(-1); }, [query]);

  /* Closed by a pointer landing anywhere else, and by Escape. Deliberately not
     by the input losing focus: the rows are links and blur fires before the
     click that follows one, which would close the panel out from under the row
     being clicked. */
  React.useEffect(() => {
    if (!open) return;
    const away = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("pointerdown", away);
    return () => document.removeEventListener("pointerdown", away);
  }, [open]);

  // Arrowing past the visible rows has to scroll them into view, or the
  // selection walks out of the panel and Enter opens something unseen.
  React.useEffect(() => {
    const list = listRef.current;
    if (active < 0 || !list) return;
    const row = list.children[active];
    if (row && row.scrollIntoView) row.scrollIntoView({ block: "nearest" });
  }, [active]);

  const submit = (e) => {
    e.preventDefault();
    const picked = shown[active];
    // Enter on an arrowed row opens that opening, in the tab the row itself
    // would have opened. Enter with nothing picked is the search it always was.
    if (picked) {
      window.open(picked.url, "_blank", "noopener,noreferrer");
      setOpen(false);
      return;
    }
    window.location.href = CAR_searchUrl(query);
  };

  const onKeyDown = (e) => {
    if (e.key === "Escape") { setOpen(false); setActive(-1); return; }
    if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
    if (!shown.length) return;
    // Without this the caret jumps to the ends of the field instead.
    e.preventDefault();
    setOpen(true);
    const step = e.key === "ArrowDown" ? 1 : -1;
    setActive((i) => {
      const n = shown.length;
      const next = i + step;
      return next < 0 ? n - 1 : next >= n ? 0 : next;
    });
  };

  /* The panel's own heading, which is also the count: it says how many openings
     answered the term, including the ones past the six rows that fit. It goes
     when there is nothing to count, where it would only repeat the line below
     it ("0 vagas encontradas" over "Nenhuma vaga encontrada"). */
  const headLabel = feed.status === "loading" ? "Buscando no InHire"
    : feed.status === "error" || hits.length === 0 ? ""
    : !typed ? "Vagas abertas no InHire"
    : hits.length === 1 ? "1 vaga encontrada"
    : `${hits.length} vagas encontradas`;

  return (
    <section className="car-hero">
      <div className="container">
        <div className="car-hero__inner">
          <span className="eyebrow">Trabalhe conosco</span>
          <h1 className="car-hero__title">
            <CAR_Typewriter />
          </h1>
          <p className="car-hero__sub">
            Somos a travel tech que coloca a marca das agências na frente. Venha criar
            produtos que milhares de pessoas usam todos os dias para vender viagens.
          </p>

          <div className="car-search-wrap" ref={wrapRef}>
            <form className="car-search" role="search" onSubmit={submit}>
              <span className="car-search__ic"><SIcon name="search" /></span>
              <div className="car-search__field">
                <input
                  type="text"
                  value={query}
                  onChange={(e) => { setQuery(e.target.value); setOpen(true); }}
                  onFocus={() => setOpen(true)}
                  onKeyDown={onKeyDown}
                  placeholder="Busque por cargo ou cidade"
                  aria-label="Buscar vagas"
                  role="combobox"
                  aria-expanded={open}
                  aria-controls="car-sg-list"
                  aria-autocomplete="list"
                  aria-activedescendant={active >= 0 ? `car-sg-opt-${active}` : undefined}
                  autoComplete="off" />
              </div>
              <button type="submit" className="btn btn--blue car-search__btn">
                Buscar vagas
                <span className="btn__arrow"><SIcon name="btnArrow" /></span>
              </button>
            </form>

            {open &&
            <div className="car-sg">
              {headLabel &&
                <div className="car-sg__head">{headLabel}</div>}

              {/* The options and nothing else, so the arrow keys can index the
                  children of this element directly. */}
              <div className="car-sg__list" id="car-sg-list" role="listbox"
                aria-label="Vagas disponíveis no InHire" ref={listRef}>
                {shown.map((job, i) => (
                  <CAR_SuggestRow
                    key={job.id}
                    job={job}
                    query={query}
                    active={i === active}
                    id={`car-sg-opt-${i}`}
                    onHover={() => setActive(i)}
                    onPick={() => setOpen(false)} />
                ))}
              </div>

              {feed.status === "error" &&
                <p className="car-sg__empty">
                  Não foi possível carregar as vagas agora.{" "}
                  <a href={CAR_IH.SITE + "/vagas"} target="_blank" rel="noopener noreferrer">
                    Ver as vagas direto no InHire
                  </a>
                </p>}

              {feed.status === "ready" && jobs.length === 0 &&
                <p className="car-sg__empty">
                  Nenhuma vaga aberta no momento. O banco de talentos, no fim desta
                  página, fica aberto sempre.
                </p>}

              {feed.status === "ready" && jobs.length > 0 && hits.length === 0 &&
                <p className="car-sg__empty">Nenhuma vaga encontrada.</p>}

              <div className="car-sg__foot">
                <a className="car-sg__all" href="Vagas.html">
                  Explorar todas as vagas
                  <SIcon name="arrowRight" size={17} />
                </a>
              </div>
            </div>}
          </div>

          <a className="car-ghost-btn car-hero__explore" href="Vagas.html">
            Ver todas as vagas
            <SIcon name="arrowRight" size={17} />
          </a>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ carousel */
function CAR_Carousel() {
  const [i, setI] = React.useState(0);
  const [tookOver, setTookOver] = React.useState(false);
  const [hovering, setHovering] = React.useState(false);
  const count = CAR_CULTURE_PHOTOS.length;
  const paused = tookOver || hovering;
  // Manual pick: show that photo and hand the carousel over to the visitor.
  const go = (n) => { setI((n + count) % count); setTookOver(true); };

  React.useEffect(() => {
    if (paused || prefersReducedMotion()) return;
    const id = setTimeout(() => setI((n) => (n + 1) % count), CAR_PHOTO_MS);
    return () => clearTimeout(id);
  }, [i, paused]);

  return (
    <div className="car-carousel"
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
      onFocusCapture={() => setHovering(true)}
      onBlurCapture={() => setHovering(false)}>
      <div className="car-carousel__viewport">
        <div
          className="car-carousel__track"
          style={{ transform: `translateX(-${i * 100}%)` }}>
          {CAR_CULTURE_PHOTOS.map((src, idx) => (
            <div className="car-carousel__slide" key={idx}>
              {/* Decorative: the section copy carries the message, so the
                  photos are not announced and carry no caption. */}
              <image-slot src={src} seed={idx}></image-slot>
            </div>
          ))}
        </div>
      </div>
      <div className="car-carousel__bar">
        <div className="car-carousel__nav">
          <button type="button" className="car-carousel__btn" aria-label="Foto anterior" onClick={() => go(i - 1)}>
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg>
          </button>
          <button type="button" className="car-carousel__btn" aria-label="Próxima foto" onClick={() => go(i + 1)}>
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6" /></svg>
          </button>
        </div>
        <div className="car-carousel__dots">
          {CAR_CULTURE_PHOTOS.map((_, idx) => (
            <button
              key={idx}
              type="button"
              aria-label={`Ir para a foto ${idx + 1}`}
              className={`car-carousel__dot ${idx === i ? "car-carousel__dot--active" : ""}`}
              onClick={() => go(idx)} />
          ))}
        </div>
      </div>
    </div>
  );
}

/* ------------------------------------------------------------ culture
   Sits where the openings preview used to be. That preview was a second copy
   of Vagas.html, kept in sync by hand and one more place for the list to go
   stale; the hero now sends people straight there instead. */
function CAR_Culture() {
  return (
    <section className="section section--alt" id="cultura">
      <div className="container">
        <div className="car-culture__grid">
          <Reveal as="div"><CAR_Carousel /></Reveal>
          <Reveal as="div" className="car-culture__copy" delay={80}>
            <span className="eyebrow">Nossa cultura</span>
            <h2 className="car-h2">Um time que vende junto, aprende junto e cresce junto</h2>
            <p className="car-p">
              Trabalhamos com liberdade e responsabilidade. Aqui, a sua ideia chega ao
              produto rápido, o feedback é direto e o impacto do seu trabalho aparece na
              vida de milhares de agências parceiras.
            </p>
            <p className="car-culture__count">
              <strong>+120</strong> pessoas na equipe
            </p>
          </Reveal>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ diversity
   Full-bleed artwork with the copy centred in the black void at its middle.
   The section is all image and type, so it carries no .container: the head
   positions itself against the viewport, which is what keeps it inside the
   void as the image is cropped. */
function CAR_Diversity() {
  return (
    <section className="car-diversity" id="diversidade">
      <Reveal as="div" className="car-diversity__head">
        <span className="eyebrow eyebrow--invert">Quem somos</span>
        <h2 className="car-h2 car-h2--invert">
          Diversidade é como a gente pensa melhor para todos
        </h2>
        <p className="car-p car-p--invert">
          Times diversos tomam decisões mais ricas. Por isso fazemos questão de
          construir um ambiente onde cada pessoa cabe do jeito que é.
        </p>
      </Reveal>
    </section>
  );
}

/* ------------------------------------------------------------ values
   O guia institucional inteiro em uma faixa: missão e visão em cima, os seis
   valores embaixo. Os três vinham de lugares diferentes (os valores estavam
   aqui, reescritos, e missão e visão não estavam em nenhuma página), e é a
   página de vagas que precisa deles juntos: quem lê está decidindo se quer
   trabalhar aqui, não comprando nada. */
function CAR_Values() {
  return (
    <section className="section car-values" id="guia">
      <div className="container">
        <Reveal as="div" className="car-values__head">
          <span className="eyebrow">Nosso guia</span>
          <h2 className="car-h2">A missão, a visão e os valores que guiam todas as nossas decisões</h2>
        </Reveal>
        <div className="car-guide">
          <Reveal as="div" className="car-guide__item">
            <h3 className="eyebrow car-guide__label">Missão</h3>
            <p className="car-guide__txt">{CAR_GUIDE.missao}</p>
          </Reveal>
          <Reveal as="div" className="car-guide__item" delay={80}>
            <h3 className="eyebrow car-guide__label">Visão</h3>
            <p className="car-guide__txt">{CAR_GUIDE.visao}</p>
          </Reveal>
        </div>
        <h3 className="eyebrow car-guide__label car-values__label">Valores</h3>
        <div className="car-values__list">
          {CAR_VALUES.map((v) => (
            <div className="car-value" key={v.n}>
              <span className="car-value__n">{v.n}</span>
              <span className="car-value__name">{v.name}</span>
              <p className="car-value__txt">{v.txt}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ careers
   Sits between the blue values panel and the benefits grid. It answers the one
   question the rest of the page leaves open: the openings live on Vagas.html
   and change week to week, so a candidate reading this page has no way of
   seeing the shape of the company behind them. The areas do not change week to
   week, so they can be stated here.
   The CTA is the same link the hero carries, on purpose: this is the second
   place someone decides they want to look, and it is a long scroll back up. */
function CAR_Areas() {
  return (
    <section className="section car-areas" id="carreiras">
      <div className="container">
        <Reveal as="div" className="car-sec-head">
          <span className="eyebrow">Carreiras</span>
          <h2 className="car-h2">As áreas que constroem a Just Travel</h2>
          <p className="car-p">
            A empresa se organiza em dez áreas que trabalham juntas o tempo todo. É
            nelas que as vagas abrem, então vale olhar onde a sua experiência se
            encaixa antes de se candidatar.
          </p>
        </Reveal>
        <div className="car-areas__grid">
          {CAR_AREAS.map((a, idx) => (
            <Reveal as="div" className="car-area" key={a.name} delay={(idx % 5) * 60}>
              {/* Decorative, so no alt: the area's name is right below it as
                  real text, and the frame is not a claim about who works
                  where. Lazy, because all ten sit well below the fold. */}
              <span className="car-area__shot">
                <img src={a.photo} alt="" loading="lazy" decoding="async" />
              </span>
              <span className="car-area__foot">
                {/* The area's glyph as a watermark rather than a chip beside the
                    name: it is drawn large in the dark band under the
                    photograph, cropped by the card's bottom edge, the same
                    treatment as the catalogue's .prod__glyph on the landing. It
                    is absolute against the card, so it sits under the name
                    without the name having to leave room for it. */}
                <span className="car-area__mark" aria-hidden="true">
                  <SIcon name={a.icon} size={null} />
                </span>
                <h3 className="car-area__name">{a.name}</h3>
              </span>
            </Reveal>
          ))}
        </div>
        <Reveal as="div" className="car-areas__cta">
          <a className="car-ghost-btn" href="Vagas.html">
            Ver vagas abertas
            <SIcon name="arrowRight" size={17} />
          </a>
        </Reveal>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ benefits */
function CAR_Benefits() {
  return (
    <section className="section" id="beneficios">
      <div className="container">
        <Reveal as="div" className="car-sec-head">
          <span className="eyebrow">Benefícios</span>
          <h2 className="car-h2">Feito para você fazer o seu melhor trabalho</h2>
          <p className="car-p">Cuidamos de quem constrói a Just Travel, dentro e fora do expediente.</p>
        </Reveal>
        <div className="car-benefits__grid">
          {CAR_BENEFITS.map((b, idx) => (
            /* Copy first in the source, glyph second: the tile reads title,
               text, illustration, and the grid puts the mark on the right. */
            <Reveal as="div" className="car-benefit" key={idx} delay={(idx % 3) * 60}>
              <div className="car-benefit__copy">
                <h3 className="car-benefit__t">{b.t}</h3>
                <p className="car-benefit__d">{b.d}</p>
              </div>
              {/* Unsized: the stylesheet scales the glyph against the card. */}
              <span className="car-benefit__ic"><SIcon name={b.icon} size={null} /></span>
            </Reveal>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ channels */
function CAR_Channels() {
  /* The mark flies in from off the band, so before the animation it sits
     outside the section, which clips it. That is why the observer watches the
     section and not the mark: an observer on the mark would be waiting for a
     box that never enters the viewport, and the plane would never take off. */
  const [secRef, secIn] = useInView({ threshold: 0.25 });
  return (
    <section className={`section car-channels${secIn ? " is-in" : ""}`} ref={secRef}>
      <div className="container">
        <div className="car-brand-sticker">
          <img className="car-brand-sticker__mark" src="assets/logo-mark.png"
            alt="Just Travel" width="56" height="56" />
        </div>
        <div className="car-channels__row">
          {CAR_CHANNELS.map((c, idx) => (
            <a className="car-channel" href={c.href} aria-label={c.label} key={idx}
              target="_blank" rel="noopener noreferrer">
              {/* Every colour in here is a literal on purpose: these are the
                  networks' own brand colours, so they hold in both themes and
                  are not ours to re-theme. */}
              <span className="car-channel__mark">
                <svg width="96" height="96" viewBox="0 0 48 48" aria-hidden="true">
                  {c.art}
                </svg>
              </span>
            </a>
          ))}
        </div>
        <div className="car-channels__caption">
          <span className="eyebrow">Acompanhe a gente</span>
          <p className="car-p">
            Bastidores, vagas em primeira mão e a cultura Just Travel nas nossas redes.
          </p>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ talent bank

   This was a form that answered every submit with "Inscrição recebida! Você
   está no nosso banco de talentos" while discarding all three fields, and then
   a mailto handoff once that was found out. Neither was needed: InHire keeps an
   open-ended "Banco de Talentos" opening of its own, with the CV upload, the
   diversity questions and the LGPD notice the ATS already handles. Sending
   people there is the only version where the candidacy actually lands
   somewhere, and it keeps every applicant in one pipeline.

   The link is the opening's own address, see TALENT_POOL in jobs-data.js: it
   used to be resolved from the feed at runtime, which built it without the slug
   InHire needs and left the button opening nothing.
   ------------------------------------------------------------ */
function CAR_Talent() {
  return (
    <section className="section section--alt">
      <div className="container">
        <div className="car-talent__grid car-talent__grid--cta">
          <div className="car-talent__copy">
            <span className="eyebrow">Banco de talentos</span>
            <h2 className="car-h2">Não achou a vaga certa? A gente quer te conhecer.</h2>
            <p className="car-p">
              Cadastre o seu perfil no nosso banco de talentos. Ele fica com o time de
              recrutamento e, quando abrir uma vaga com a sua cara, você é uma das
              primeiras pessoas a saber.
            </p>
            <a className="btn btn--blue btn--lg" href={CAR_IH.TALENT_POOL} target="_blank" rel="noopener noreferrer">
              Entrar no banco de talentos
              <span className="btn__arrow"><SIcon name="btnArrow" /></span>
            </a>
            <p className="car-form__fine">
              O cadastro acontece no InHire, onde a gente cuida de todas as candidaturas.
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------ app */
function CARApp() {
  const [query, setQuery] = React.useState("");
  // One fetch for the page, on mount, feeding the hero autocomplete.
  const feed = CAR_IH.useJobs();

  return (
    <PageShell>
      <CAR_Hero query={query} setQuery={setQuery} feed={feed} />
      <CAR_Culture />
      <CAR_Diversity />
      <CAR_Values />
      <CAR_Areas />
      <CAR_Benefits />
      <CAR_Channels />
      <CAR_Talent />
      </PageShell>
  );
}

(function () {
  const el = document.getElementById("app");
  if (!el) return;
  ReactDOM.createRoot(el).render(<CARApp />);
})();
