/* /perfiles: search + flip-card carousel — docs/BACKLOG.md's Data section, hub-dropdown +
   /perfiles redesign, product-designer-ux mockup approved by Samuel 2026-08-13. Ported from that
   mockup's own prototype CSS (kept 1:1 on colors/values/breakpoints, "no rediseñes nada" per the
   dispatch for this round) — the only real structural change is `.t-front` no longer being the
   card's outer <a> itself: see .t-card-linkarea's own comment below for why.

   Linked via <link rel="stylesheet"> in seeker/directorio.html's/-en.html's <head> (spliced in
   server-side by api/get-profile.js's buildSeekerDirectoryHeadHtml, same pattern already used
   for /shared-select.css elsewhere) — /seo-pages.css (loaded first) already supplies --teal/
   --teal-dark/--cyan/--ink/--white/--muted and the page's own dotted background, .kreer-dot,
   .container, .breadcrumb, h1, .btn-primary. Only the two variables that file doesn't define
   (--slate, --border) are added here. */

:root {
  --slate: #46566F;
  --border: rgba(255,255,255,0.09);
}

/* Real "K·reer" wordmark mention never splitting across a line break — CLAUDE.md's permanent
   rule. Paired with KREER_BRAND_HTML (api/get-profile.js) / KREER_BRAND_HTML (directory-
   carousel.js's own client-side copy). */
.kb { white-space: nowrap; }

/* Container width override (2026-08-14, second round the same day) — this page's own
   /seo-pages.css .container is 780px sitewide; the approved mockup round calibrated this
   carousel's composition (~944px usable band, ~227px cards) at 992px instead, recovering a
   second skill chip on desktop that 780px's narrower ~173px cards were clipping via overflow.
   Safe to override here rather than in /seo-pages.css itself: this file is only ever linked from
   seeker/directorio.html/-en.html (see this file's own header comment), so no other page sharing
   .container sitewide is affected, and this rule wins on source order (this stylesheet loads
   after /seo-pages.css, same specificity). `position: relative; z-index: 1` isn't a layout need
   of its own — see the Umbral atmosphere block below for why .container (not #dir-atmosphere)
   is where that stacking promotion belongs now that the atmosphere is page-wide. */
.container { max-width: 992px; margin: 0 auto; padding: 48px 24px 72px; position: relative; z-index: 1; }

/* ---------- Search bar (same recipe as the hub's own .hub-searchbar) ---------- */
.dir-head { max-width: 640px; margin: 0 auto 6px; text-align: center; }
.dir-h1 { font-size: 22px; font-weight: 800; letter-spacing: -0.3px; margin: 0 0 8px; line-height: 1.25; text-wrap: balance; }
/* .dir-sub (the "Perfiles públicos de resultados..." subtitle paragraph) removed 2026-08-14,
   Samuel's direct live-site feedback — no replacement, .dir-h1's own margin-bottom (8px) plus
   .dir-head's own margin-bottom (6px, above) already leave a normal gap above the search bar. */
.dir-search { max-width: 560px; margin: 0 auto; }
.searchbar { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.045); border: 1.5px solid rgba(22,184,166,0.42); border-radius: 14px; padding: 6px 6px 6px 14px; box-shadow: 0 0 0 1px rgba(22,184,166,0.15), 0 10px 34px rgba(22,184,166,0.2); transition: border-color 0.15s, background 0.15s; flex: 1; min-width: 0; }
.searchbar:focus-within { border-color: var(--teal); background: rgba(255,255,255,0.06); }
.searchbar svg { color: var(--muted); flex-shrink: 0; }
.searchbar input { flex: 1; min-width: 0; background: none; border: none; outline: none; font-family: 'Sora', sans-serif; font-size: 16px; color: var(--white); padding: 9px 0; }
.searchbar input::placeholder { color: var(--slate); }

/* ---------- Language filter (2026-08-14, docs/BACKLOG.md "Language filter for /perfiles",
   product-designer-ux/Freya mockup, 3 decisions Samuel closed the same day) — `#dir-lang-filter`
   (api/get-profile.js's buildSeekerDirectoryMainHtml) is a real <select>, enhanced client-side by
   /shared-dropdown.js into the site's own shared dark/teal floating panel (`.kreer-dd-panel`,
   /shared-select.css) — CLAUDE.md's permanent rule for every <select> sitewide — so no new panel
   CSS is needed here, only this compact trigger sizing. No per-option count (Samuel's second
   closed decision: "sin contadores", unlike the mockup's own `.lo-count` prototype). NOT a 7th
   chip type: the chip row below is already at its own width budget
   (DIRECTORY_SKILL_CHIP_LIMIT's own comment, api/get-profile.js) and language/result-type are
   different query dimensions (who someone IS / what they've done) from the chips' own free-text-
   skill axis. */
/* ---------- Result-type filter (2026-08-15, docs/BACKLOG.md "Result-type filter for /perfiles",
   product-designer-ux/Freya mockup approved by Samuel) — `#dir-rtype-filter` (same file), same
   real-<select>-enhanced-by-/shared-dropdown.js pattern as the language filter above. Two-row
   search band (this round's own change, replacing the single-row layout the language filter
   shipped with the day before): with a 2nd categorical dropdown added, one 560px row can no
   longer fit search + 2 selects without crushing the search bar. Row 1: search bar alone, full
   width (recovers the width it used to share with the language select). Row 2: the two
   categorical filters together, side by side, "Resultado" first (Samuel: "core del negocio",
   framed as more central than language) — neither joins .chip-row (single-select, a different
   query dimension, and that row is already at its own width budget). */
.search-band { display: flex; flex-direction: column; gap: 8px; }
.filter-band { display: flex; gap: 8px; align-items: stretch; }
.dd { flex: 1; min-width: 0; }
.dd .kreer-dd-trigger { height: 100%; display: flex; align-items: center; }
@media (max-width: 560px) {
  .filter-band { flex-direction: column; }
  .dd { width: 100%; }
}

/* ---------- Filter chips ---------- */
/* Real bug found live 2026-08-14 (Samuel reported: "Todas" cut off / off-screen on some
   windows, no hint that the row scrolls): `.chip-row` used to center its own content past
   720px width via `justify-content:center`, unconditionally — a fixed-breakpoint heuristic like
   the carousel's old `few-N` arrow-hiding rule, same class of mistake. That heuristic assumed
   "wide enough viewport" meant "fits without scrolling", but this row's own `max-width:760px`
   budget is fixed regardless of viewport width, and ES chip labels in particular ("Con
   resultados certificados" alone) can push real content past 760px even on a plainly wide
   desktop window. When a centered flex row overflows its own scroll container, the browser
   still clamps `scrollLeft` to 0 at minimum, but 0 no longer means "start of content" once
   centering has shifted everything left of that point (confirmed live: `scrollLeft` really was
   0, yet the first chip's own bounding box started ~35px to the left of the visible viewport,
   permanently unreachable by scrolling). `#chip-row-wrap` (new wrapper, api/get-profile.js) now
   carries the shared `max-width:760px`/centering margin instead of `.chip-row` itself, and
   directory-carousel.js's own initChipOverflowUI() toggles a real `has-overflow` class on it
   based on an actual scrollWidth/clientWidth check (same dynamic-overflow pattern as the
   carousel arrows' `no-overflow` class above) — centered only when it genuinely fits, left-
   aligned (so the first chip starts exactly at scrollLeft 0) whenever it doesn't, at any
   viewport width, not just below/above one fixed breakpoint. */
.chip-row-wrap { position: relative; margin: 16px auto 4px; max-width: 760px; }
.chip-row { display: flex; flex-wrap: nowrap; gap: 7px; overflow-x: auto; padding-bottom: 4px; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }
@media (min-width: 720px) { .chip-row-wrap.no-overflow .chip-row { justify-content: center; } }
/* Scroll-affordance fade (2026-08-14, same bug as above): a soft gradient over each edge,
   shown only while there's really more to scroll to on that side — toggled by the same
   initChipOverflowUI() via `has-more-l`/`has-more-r` classes on #chip-row-wrap, recomputed on
   scroll/resize/chip-filter-change. #0D1826 matches /seo-pages.css's own body background-color
   (the plain base tone under the dotted texture/Umbral glow this row sits on) — an approximation
   of the true layered background, same "close enough, not pixel-perfect against an animated
   background" tradeoff any edge-fade over a textured surface makes. */
.chip-fade { position: absolute; top: 0; bottom: 4px; width: 28px; pointer-events: none; opacity: 0; transition: opacity 0.15s; }
.chip-fade-l { left: 0; background: linear-gradient(90deg, #0D1826, rgba(13,24,38,0)); }
.chip-fade-r { right: 0; background: linear-gradient(270deg, #0D1826, rgba(13,24,38,0)); }
.chip-row-wrap.has-more-l .chip-fade-l { opacity: 1; }
.chip-row-wrap.has-more-r .chip-fade-r { opacity: 1; }
.fchip { flex-shrink: 0; font-family: 'Sora', sans-serif; font-size: 11px; font-weight: 700; color: var(--muted); background: rgba(255,255,255,0.04); border: 1px solid var(--border); border-radius: 100px; padding: 6px 13px; cursor: pointer; transition: color 0.15s, border-color 0.15s, background 0.15s; }
.fchip:hover { color: var(--white); border-color: rgba(255,255,255,0.22); }
/* Active-chip glow, directional teal->gold to match the reference — same amber family reserved
   sitewide for paid-contact-backed connection; flagged as an open question in this round's own
   report (decorative-only use here, not a claim of paid backing), not resolved unilaterally. */
.fchip.on { color: #0D1826; background: linear-gradient(90deg, var(--teal), #F0C878); border-color: transparent; box-shadow: -4px 0 12px rgba(22,184,166,0.4), 4px 0 12px rgba(240,200,120,0.28); }

/* ---------- Atmospheric background: "Umbral" (2026-08-14, replaces the waves+bubbles
   background from the previous round — Samuel's explicit call, ported from the same mockup's
   own Propuesta 2 for the hub/journey, approved 2026-08-13, brought here 2026-08-14). Ambient
   light + two rings echoing the brand dot's circular geometry, each with a brighter arc where
   the light touches it, plus a soft floor wash — all fixed-proportion CSS shapes, zero stretched
   SVG, zero distortion risk.

   SIXTH PASS, 2026-08-14 (second round the same day) — supersedes the fifth pass in its entirety
   (the "manufacture a padding-bottom buffer and corner-anchor both rings to #dir-atmosphere's own
   bottom edge" approach documented at length in this file's own prior history). Samuel's explicit
   direction on top of that fifth pass: the atmosphere shouldn't be scoped to a panel at all — it
   covers the whole page, from the nav bar to the footer, at any scroll position, independent of
   how many cards render. Approved via a real mockup round (scratchpad
   perfiles-pocos-resultados.html, its own Δ1/Δ2/Δ2b), ported here verbatim on values.

   .umb-aura/.umb-trace are now `position: fixed; inset: 0;` instead of scoped to #dir-atmosphere
   (`position: absolute`) — #dir-atmosphere itself goes back to being a plain, box-less wrapper
   (no padding, no background-image, no z-index-lifting rule for its children: with the aura at
   z-index:-1, everything else already paints above it by default, nothing needs promoting). Being
   fixed means the ring/light/floor geometry is anchored to the VIEWPORT's own corners, not to any
   element's box, so it's correct at any scroll position and with any card count by construction —
   the exact problem the fifth pass's own padding-buffer hack was working around (a ring anchored
   to a sibling's box breaks the moment that sibling's layout changes, e.g. filtering down to 1
   result). The values below (600px/680px rings, corner offsets) are verbatim from the approved
   mockup's own boceto, not the fifth pass's shrunk-to-240px compromise — with the frame itself
   gone, the viewport is what clips the rings into corner vignettes now, so there's no longer a
   reason to keep them small enough to hide inside a manufactured buffer. `.container` (below) gets
   its own `position: relative; z-index: 1` specifically so it (and everything inside it, including
   these fixed-position descendants) paints as one stacking unit above `.nav-bar`/`.footer`'s own
   plain, non-positioned boxes — those two are otherwise transparent bars with no real background
   to hide behind, so this doesn't visually box anything in, it's what lets the ring/light bleed
   over the nav/footer regions the way "covers the whole page" describes.

   #dir-atmosphere still wraps dir-head/dir-search/chip-row/car-wrap/car-dots/car-hint (moved
   inside it in buildSeekerDirectoryMainHtml, api/get-profile.js) instead of just the carousel row
   alone — unchanged from the fifth pass, still a deliberate adaptation, not a size-for-size port
   of the mockup's own #dir-frame (which only wrapped the carousel itself, not the search/chips
   above it). */
#dir-atmosphere { position: relative; margin-top: 14px; }
.car-empty-msg { color: var(--muted); font-size: 13px; text-align: center; }
.umb-aura { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.umb-light { position: absolute; top: -200px; left: 50%; transform: translateX(-38%); width: 760px; height: 500px; border-radius: 50%; background: radial-gradient(closest-side, rgba(22,184,166,0.21), rgba(22,184,166,0.07) 55%, transparent 78%); filter: blur(40px); mix-blend-mode: screen; animation: umb-breathe 14s ease-in-out infinite; }
@keyframes umb-breathe {
  0%, 100% { opacity: 0.85; }
  50%      { opacity: 1; }
}
/* Both rings corner-anchored to the fixed .umb-aura's own box, which is now the viewport itself
   (inset:0 on a position:fixed element) — 600px top-right (its brighter arc, kept the more
   saturated ring for visual continuity with prior passes), 680px bottom-left, verbatim from the
   approved mockup's own boceto values. */
.umb-ring { position: absolute; top: -300px; right: -180px; width: 600px; height: 600px; border-radius: 50%; border: 1.5px solid rgba(84,230,208,0.17); }
.umb-ring-arc { position: absolute; top: -300px; right: -180px; width: 600px; height: 600px; border-radius: 50%; border: 2.5px solid transparent; border-left-color: rgba(84,230,208,0.62); border-top-color: rgba(84,230,208,0.18); transform: rotate(-16deg); filter: blur(2.5px); mix-blend-mode: screen; }
/* 0.09, not the mockup's own hub-context 0.12: adaptation, not deviation — the hub's identity
   cards have glass (backdrop-blur) that softens this ring where it passes behind them; these
   flip-cards have no glass, so the same 0.12 read as a hard line through a ghost button's
   transparent background. */
.umb-ring-2 { position: absolute; bottom: -300px; left: -260px; width: 680px; height: 680px; border-radius: 50%; border: 1px solid rgba(84,230,208,0.09); }
/* rotate(52deg): kept from the mockup's own value (still reads as an "echo" of the brand-dot
   geometry at this smaller size). */
.umb-ring-2-arc { position: absolute; bottom: -300px; left: -260px; width: 680px; height: 680px; border-radius: 50%; border: 2.5px solid transparent; border-right-color: rgba(22,184,166,0.42); border-bottom-color: rgba(22,184,166,0.13); transform: rotate(52deg); filter: blur(2.5px); mix-blend-mode: screen; }
.umb-floor { position: absolute; left: 8%; right: 8%; bottom: -1px; height: 120px; background: radial-gradient(ellipse at 50% 100%, rgba(22,184,166,0.18), transparent 70%); filter: blur(18px); mix-blend-mode: screen; }
/* Trace layer: a second copy of the same rings, now also `position: fixed` (same viewport-corner
   anchoring as .umb-aura above, same coordinates, so each trace arc lines up pixel-for-pixel with
   its own background ring), ABOVE the content (z-index 2, pointer-events none), mix-blend-mode
   screen so the line would read against the navy card surface but disappear over already-light
   text/chips. The elliptical mask below only reveals it in the central band around the initial
   card row's own scroll position — real work again now that the rings cross that band (unlike the
   fifth pass, where both rings lived in a bottom buffer the mask had already faded out by).
   .umb-ring-arc's trace override switched from border-right/-bottom to border-left/-top to match
   .umb-ring-arc's own top-right anchor above (was bottom-right in the fifth pass); .umb-ring-2-arc's
   override is untouched, already border-right/-bottom to match its own unchanged bottom-left
   anchor. */
.umb-trace {
  position: fixed; inset: 0; z-index: 2; overflow: hidden; pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 58% 34% at 50% 58%, rgba(0,0,0,1) 40%, rgba(0,0,0,0) 78%);
  mask-image: radial-gradient(ellipse 58% 34% at 50% 58%, rgba(0,0,0,1) 40%, rgba(0,0,0,0) 78%);
}
.umb-trace .umb-ring { border-color: rgba(84,230,208,0.14); mix-blend-mode: screen; }
.umb-trace .umb-ring-arc { border-left-color: rgba(84,230,208,0.4); border-top-color: rgba(84,230,208,0.13); filter: blur(1.2px); }
.umb-trace .umb-ring-2 { border-color: rgba(84,230,208,0.11); mix-blend-mode: screen; }
.umb-trace .umb-ring-2-arc { border-right-color: rgba(22,184,166,0.3); border-bottom-color: rgba(22,184,166,0.1); filter: blur(1.2px); }
@media (max-width: 560px) {
  .umb-light { width: 440px; height: 320px; top: -130px; transform: translateX(-42%); }
  .umb-ring, .umb-ring-arc { width: 400px; height: 400px; top: -220px; right: -130px; }
  .umb-ring-2, .umb-ring-2-arc { width: 440px; height: 440px; bottom: -240px; left: -190px; }
  .umb-floor { height: 90px; }
}

/* ---------- Carousel ---------- */
.car-wrap { position: relative; margin-top: 22px; }
.car-row { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; padding: 4px 2px 8px; scrollbar-width: none; }
.car-row::-webkit-scrollbar { display: none; }

/* Few-result row centering + arrow/dot suppression (2026-08-14, second round the same day,
   approved mockup's own Δ3/Δ3b). #car-wrap carries a `few-N` class (N = card count, only ever
   0-3) — set server-side in buildSeekerDirectoryMainHtml (api/get-profile.js) for the very first
   paint, kept in sync client-side by directory-carousel.js's own render() on every chip
   click/search keystroke, same "server sets it once, client maintains it" split the rest of this
   file already uses for #car-row's own content. Only centers when the count actually fits without
   horizontal scroll at a given breakpoint — centering a row that still needs to scroll would hide
   its own starting edge, so each breakpoint resets back to flex-start once the fixed 4-up/2-up
   grid can no longer fit that count on one screen without scrolling. */
.car-wrap.few-1 .car-row, .car-wrap.few-2 .car-row, .car-wrap.few-3 .car-row { justify-content: center; }
@media (max-width: 900px) { .car-wrap.few-3 .car-row { justify-content: flex-start; } }
@media (max-width: 560px) { .car-wrap.few-2 .car-row { justify-content: flex-start; } }
/* Arrows/dots only ever make sense when there's something to scroll past. Without this, a 1-card
   result used to show two disabled arrows floating over empty space and a single, pointless dot
   underneath. `.car-dots` is a sibling of `.car-wrap` (not a descendant, see the two elements'
   own markup in buildSeekerDirectoryMainHtml), hence the `~` general-sibling combinator instead
   of a descendant selector. */
.car-wrap.few-0 .car-arrow, .car-wrap.few-1 .car-arrow, .car-wrap.few-2 .car-arrow, .car-wrap.few-3 .car-arrow { display: none; }
.car-wrap.few-0 ~ .car-dots, .car-wrap.few-1 ~ .car-dots, .car-wrap.few-2 ~ .car-dots, .car-wrap.few-3 ~ .car-dots { display: none; }
@media (max-width: 900px) and (min-width: 561px) {
  .car-wrap.few-3 .car-arrow { display: flex; }
  .car-wrap.few-3 ~ .car-dots { display: flex; }
}
@media (max-width: 560px) {
  .car-wrap.few-2 .car-arrow, .car-wrap.few-3 .car-arrow { display: flex; }
  .car-wrap.few-2 ~ .car-dots, .car-wrap.few-3 ~ .car-dots { display: flex; }
}
/* Generalizes the few-N arrow/dot suppression above (2026-08-14, carousel-arrow bug fix) to any
   card count, not just 0-3: `#car-wrap` also carries a dynamic `no-overflow` class, set/kept in
   sync by directory-carousel.js's own updateArrows() (a real scrollWidth/clientWidth check, not
   a fixed count) — for the case few-N can't anticipate at SSR time: enough cards to exactly fill
   one row at the visitor's actual breakpoint (e.g. 4 cards on desktop's 4-up grid) with zero
   scroll possible. Real bug found live 2026-08-14: those arrows were already correctly
   `disabled` (updateArrows()'s own comment), but CSS only dimmed a disabled arrow instead of
   hiding it, so it stayed visible-but-inert — read as "the arrows don't respond to clicks"
   instead of "there's nothing to scroll to". Same "arrows/dots only make sense when there's
   something to scroll past" intent as the comment above this block, just not bounded to a count
   JS can only guess about server-side. */
.car-wrap.no-overflow .car-arrow { display: none; }
.car-wrap.no-overflow ~ .car-dots { display: none; }

/* Giro 3D — tocar la tarjeta la voltea en su lugar; el reverso condensa el detalle. Estructura:
   .t-card (contenedor, perspective) > .t-flip (el plano que gira) > .t-face.t-front (frente,
   mejora progresiva sobre .t-card-linkarea, un <a> real) + .t-face.t-back (JS-only, construido
   por directory-carousel.js, nunca presente en el HTML servido sin JS). */
.t-card { flex: 0 0 calc((100% - 36px) / 4); scroll-snap-align: start; min-width: 0; display: flex; position: relative; perspective: 1600px; transition: transform 0.15s; }
.t-card:hover { transform: translateY(-2px); }
.t-flip { flex: 1; min-width: 0; display: flex; position: relative; transform-style: preserve-3d; transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.t-card.flipped .t-flip { transform: rotateY(180deg); }
.t-face { backface-visibility: hidden; -webkit-backface-visibility: hidden; border: 1px solid var(--border); border-radius: 18px; background: rgba(255,255,255,0.08); transition: border-color 0.15s, background 0.15s; }
.t-card:hover .t-front { border-color: rgba(22,184,166,0.45); background: rgba(255,255,255,0.1); }
.t-front { position: relative; flex: 1; min-width: 0; padding: 22px 16px 16px; display: flex; flex-direction: column; align-items: center; }
/* .t-card-linkarea replaces the mockup's own <a class="t-front">: the CTA below became a real
   <a href> once the public/unauthenticated Connect-button decision landed (see
   directoryCardFrontHtml's own comment, api/get-profile.js) instead of a JS-modal button, so the
   whole-card click target had to move to its own, separate anchor — a nested <a> inside another
   <a> is invalid HTML and behaves unpredictably across browsers. Same sibling-anchors pattern
   index.html's own .people-search-row/.people-search-row-link already uses. */
.t-card-linkarea { display: flex; flex-direction: column; align-items: center; width: 100%; min-width: 0; text-align: center; text-decoration: none; color: var(--white); cursor: pointer; }
/* "Save for later" bookmark (2026-08-14, docs/BACKLOG.md /perfiles Clerk reversal) — top-right
   corner of .t-front (position:relative, above), same size/shape family as .b-close on the back
   face (26x26, rounded, subtle bg) but never in the same spot at the same time (front-only, see
   directoryCardFrontHtml's own comment on why the back face doesn't duplicate it) so there's no
   collision to worry about. Sits clear of the centered 112px avatar at every breakpoint this
   card renders at (.t-card's own min flex-basis, 560px breakpoint above, still leaves >60px of
   horizontal clearance beside the avatar). is-saved recolors it the same teal-on-teal treatment
   already used for .people-search-save-btn's own is-saved state (index.html/en.html). */
.t-save-btn { position: absolute; top: 10px; right: 10px; z-index: 2; width: 26px; height: 26px; border-radius: 8px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); color: var(--muted); cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; transition: opacity 0.3s ease; }
.t-save-btn svg { width: 13px; height: 13px; }
.t-save-btn:hover { color: var(--white); border-color: rgba(255,255,255,0.22); }
.t-save-btn.is-saved { color: var(--teal); background: rgba(22,184,166,0.12); border-color: rgba(22,184,166,0.4); }
/* Flipped-state overlap fix (2026-08-15, Samuel reported via screenshot: on Alexa Mora's flipped
   card, this button sat directly on top of .b-avatar, the small circular photo next to the name
   on the back face). Root cause, confirmed via getBoundingClientRect() math on the live page:
   .t-save-btn lives inside .t-front (this file's own comment above), whose only defense against
   showing while flipped is backface-visibility:hidden on .t-face (line ~263) — correct in most
   engines, but this exact combination (position:absolute + z-index on a descendant of a
   backface-hidden 3D-transformed ancestor) is a known WebKit/Safari rendering quirk where the
   descendant escapes that hiding and paints anyway. When it does, .t-front's own `right:10px`
   mirrors along with .t-flip's rotateY(180deg), landing the button almost exactly on .b-avatar's
   own top-left position (both 26x26, ~4px apart — verified against the live DOM, not guessed).
   Hiding it explicitly here, keyed off the same .flipped class setFlip() already toggles
   (directory-carousel.js), removes the dependency on any browser's 3D-rendering behavior being
   correct — an unambiguous 2D rule instead of a 3D one. opacity (not display:none) fades smoothly
   using the transition just added above, rather than snapping; front.inert = on (setFlip()) already
   pulls it out of tab order/AT tree while flipped, pointer-events:none here is belt-and-suspenders
   against the same WebKit-leak state specifically (inert alone doesn't stop a visual overlap). */
.t-card.flipped .t-save-btn { opacity: 0; pointer-events: none; }
/* 2026-08-14, save-icon bug fix (see directory-carousel.js's handleSaveClick/waitForClerk own
   comment): immediate feedback the instant the button is clicked, while the async Clerk SDK
   may still be loading (a real, measured ~1-3s race, not instant) — without this the button
   looked completely inert during that window, indistinguishable from the reported "does
   nothing" bug even once the underlying fix made it eventually succeed. pointer-events:none also
   guards against a double-click stacking two openCustomAuth()/openSaveCategoryModal() calls. */
.t-save-btn.is-loading { opacity: 0.45; pointer-events: none; }
@media (max-width: 900px) { .t-card { flex-basis: calc((100% - 12px) / 2); } }
@media (max-width: 560px) { .t-card { flex-basis: min(280px, 86%); } }
.t-avwrap { position: relative; width: 112px; height: 112px; margin-bottom: 14px; }
.t-avatar { width: 112px; height: 112px; border-radius: 50%; object-fit: cover; display: flex; align-items: center; justify-content: center; font-size: 34px; font-weight: 800; color: #0D1826; background: linear-gradient(135deg, var(--teal), var(--teal-dark)); }
.t-avatar.ph { border: 2px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.06); }
.t-card.tiered .t-avatar { box-shadow: 0 0 0 3px var(--teal); }
.t-seal { position: absolute; bottom: 3px; right: 3px; width: 28px; height: 28px; border-radius: 50%; background: linear-gradient(135deg, var(--cyan), var(--teal)); border: 2.5px solid #0D1826; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; color: #0D1826; }
/* 2-line clamp (2026-08-19, docs/BACKLOG.md's Kreer Card entry — same real-user complaint class
   as that entry's `.krole` follow-up, a different component here) — was a single nowrap+ellipsis
   line, confirmed via Playwright at real mobile card width (~280px, minus avatar/padding) that a
   long name genuinely overflows (`scrollWidth > clientWidth`), not a broken-ellipsis bug, just
   little visible room. `.t-front`/`.t-flip`/`.t-card` have no fixed height or aspect-ratio (unlike
   Kreer Card's `.kcard`) — `.t-flip`'s only sized child in normal flow is `.t-front` (`.t-back` is
   `position:absolute;inset:0`, contributes nothing to `.t-flip`'s own height and simply matches
   whatever height `.t-front` ends up needing), so letting this wrap to 2 lines just grows the
   front face naturally, with `.t-back` following automatically — no separate height fix needed.
   `text-align:center` on the parent `.t-card-linkarea` still centers a 2-line name the same way it
   already centers 1-line ones. */
.t-name { max-width: 100%; font-size: 16px; font-weight: 800; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.t-tier { font-size: 9px; font-weight: 700; color: var(--teal); background: rgba(22,184,166,0.1); border: 1px solid rgba(22,184,166,0.3); border-radius: 100px; padding: 3px 9px; white-space: nowrap; }
.t-pro { font-size: 9px; font-weight: 800; color: #0D1826; background: linear-gradient(135deg, #F5C36B, #F5A623); border: none; border-radius: 100px; padding: 4px 10px; white-space: nowrap; }
.t-role { max-width: 100%; font-size: 12px; color: var(--muted); line-height: 1.45; margin-top: 4px; height: 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.t-cred { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin-top: 12px; height: 23px; overflow: hidden; align-content: flex-start; max-width: 100%; }
.t-skill { font-size: 9.5px; font-weight: 700; color: var(--muted); background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); border-radius: 100px; padding: 3px 9px; white-space: nowrap; }
.t-skill.cert { color: var(--teal); border-color: rgba(22,184,166,0.35); background: rgba(22,184,166,0.08); }
.t-skill.status { font-style: italic; }
.t-sig-seal { width: 17px; height: 17px; border-radius: 50%; background: linear-gradient(135deg, var(--cyan), var(--teal)); display: inline-flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 800; color: #0D1826; flex-shrink: 0; }
.t-rtype { display: inline-flex; align-items: center; gap: 5px; font-size: 9px; font-weight: 700; color: var(--muted); background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); border-radius: 100px; padding: 2.5px 9px 2.5px 7px; white-space: nowrap; }
.t-rtype svg { width: 11px; height: 11px; flex-shrink: 0; stroke: currentColor; }
.t-rtype.cert { color: var(--teal); background: rgba(22,184,166,0.08); border-color: rgba(22,184,166,0.35); }
/* Result-type filter "why did this card match" highlight (2026-08-15, docs/BACKLOG.md
   "Result-type filter for /perfiles") — set by directory-carousel.js's ensureVisibleRtypes/
   rtypeChip whenever the "Resultado" filter is active AND this chip is the category that matched,
   even if it isn't one of the seeker's own top-2 richest categories (that fallback is what
   ensureVisibleRtypes guarantees). Same still teal tint the language filter's own trigger uses for
   "has a value" (.kreer-dd-trigger, shared-select.css) — a quiet highlight, not the amber/gold
   "certified" glow .fchip.on or .t-skill.cert use elsewhere, so it never reads as a certification
   claim. Can combine with .cert (a matched AND certified chip gets both). */
.t-rtype.match { color: var(--cyan); border-color: rgba(22,184,166,0.55); background: rgba(22,184,166,0.08); }
.t-contact { width: 100%; margin-top: 13px; }
.t-contact-pill { display: flex; align-items: center; justify-content: center; width: 100%; box-sizing: border-box; font-size: 10px; font-weight: 700; border-radius: 100px; padding: 6px 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.t-contact-pill.paid { color: var(--teal); background: rgba(22,184,166,0.07); border: 1px solid rgba(22,184,166,0.3); }
.t-contact-pill.free { color: var(--muted); background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); }
/* margin-top: auto (was a fixed 12px) — 2026-08-14, second round the same day, approved mockup's
   own Δ5: pins the CTA to the card's own bottom edge instead of trailing directly under whatever
   content happens to precede it. Deliberately does NOT shrink the button itself — the tap-target
   and visual weight of "+ Conectar" stay identical across every card in the row regardless of how
   little content a given card has; .t-front is already `display:flex; flex-direction:column`
   (above), so `margin-top:auto` on this, its last flex child, is what does the pinning. */
.t-cta { width: 100%; margin-top: auto; padding-top: 12px; display: block; }
.t-btn { display: flex; align-items: center; justify-content: center; width: 100%; box-sizing: border-box; font-size: 12px; font-weight: 800; padding: 11px 12px; border-radius: 12px; border: none; text-decoration: none; cursor: pointer; }
.t-btn.connect, .b-view { background: linear-gradient(135deg, rgba(84,230,208,0.85), rgba(22,184,166,0.8) 55%, rgba(14,156,140,0.76)); color: #fff; -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); box-shadow: inset 0 1px 0 rgba(255,255,255,0.3), 0 0 10px rgba(84,230,208,0.35), 0 0 24px 4px rgba(22,184,166,0.42), 0 6px 18px rgba(22,184,166,0.3); }
.t-btn.connect:hover, .b-view:hover { filter: brightness(1.06); }

.car-arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 5; width: 32px; height: 32px; border-radius: 50%; background: rgba(13,24,38,0.92); border: 1px solid rgba(22,184,166,0.4); color: var(--cyan); font-size: 16px; font-weight: 800; line-height: 1; display: flex; align-items: center; justify-content: center; padding: 0; transition: opacity 0.15s; cursor: pointer; font-family: 'Sora', sans-serif; }
.car-arrow-l { left: -12px; }
.car-arrow-r { right: -12px; }
.car-arrow[disabled] { opacity: 0.25; cursor: default; }
/* !important (added alongside the few-N arrow rules above): without it, a touch device inside the
   901-900...561px tablet band where .car-wrap.few-3 .car-arrow re-enables `display:flex` would win
   over this rule on specificity alone (two classes beats one), silently bringing back arrows on a
   device that can't hover anyway. */
@media (hover: none) { .car-arrow { display: none !important; } }
.car-dots { display: flex; gap: 5px; justify-content: center; align-items: center; margin-top: 12px; }
.car-dots i { width: 5px; height: 5px; border-radius: 100px; background: rgba(255,255,255,0.18); transition: background 0.15s, width 0.2s; }
.car-dots i.on { width: 16px; background: var(--teal); }
.car-hint { font-size: 10.5px; color: var(--slate); text-align: center; margin: 10px 0 0; }
/* .hint-reset (2026-08-14, second round the same day, approved mockup's own Δ4): the "Ver todas"/
   "See all" link inside the filtered-count message (see renderHint()'s own comment,
   directory-carousel.js, and buildDirectoryHintHtml's own comment, api/get-profile.js). A real
   inline <button>, styled to read as a text link so it sits naturally inside the sentence it's
   part of instead of looking like a separate UI control. */
.car-hint .hint-reset { font-family: 'Sora', sans-serif; font-size: 10.5px; font-weight: 700; color: var(--teal); background: none; border: none; padding: 0; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.car-hint .hint-reset:hover { color: var(--cyan); }

/* max-width + margin auto (2026-08-14, second round the same day, approved mockup's own Δ7): the
   dashed empty-state box no longer stretches to the full carousel width — it reads as a bounded
   message card instead of a banner. Arrows/dots/swipe-hint are already suppressed for the 0-result
   case via .car-wrap's own `few-0` class (see the few-N rules above), so there's nothing left
   around it implying there's more to scroll to. */
.car-empty { flex: 1 1 100%; max-width: 520px; margin: 0 auto; background: rgba(255,255,255,0.03); border: 1px dashed rgba(255,255,255,0.14); border-radius: 16px; padding: 26px 20px; text-align: center; }
.car-empty p { font-size: 12px; color: var(--muted); line-height: 1.6; margin: 0 0 10px; }
.car-empty button { font-family: 'Sora', sans-serif; font-size: 11px; font-weight: 700; color: var(--teal); background: rgba(22,184,166,0.1); border: 1px solid rgba(22,184,166,0.3); border-radius: 100px; padding: 6px 14px; cursor: pointer; }

/* ---------- Back face (built by directory-carousel.js only — never present without JS) ----------
   Two zones (2026-08-14, docs/BACKLOG.md "Ver perfil completo" cutoff fix, Samuel reported via
   real screenshots): .t-back itself no longer scrolls as one flex column. .b-scroll (the variable-
   length content: header, cert, chips, meta, status) is the ONLY scrolling zone; .b-actions (the
   "Ver perfil completo"/"+ Conectar" buttons) is a sibling pinned to the bottom of .t-back's own
   flex column, so it's always visible/clickable regardless of how much content a given profile
   has — previously the whole face scrolled together with the hidden-by-design scrollbar
   (scrollbar-width:none, matching the rest of this file's scroll surfaces), so a content-heavy
   profile pushed the buttons below the fold with no visible cue anything was scrollable. */
.t-back { position: absolute; inset: 0; transform: rotateY(180deg); cursor: pointer; display: flex; flex-direction: column; align-items: stretch; gap: 7px; text-align: left; padding: 13px 14px; overflow: hidden; background: linear-gradient(180deg, rgba(20,32,47,0.97), rgba(15,37,53,0.97)); border-color: rgba(22,184,166,0.3); }
.b-close { position: absolute; top: 10px; right: 10px; z-index: 1; width: 26px; height: 26px; border-radius: 50%; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); color: var(--muted); font-size: 11px; display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; font-family: 'Sora', sans-serif; }
.b-close:hover { color: var(--white); }
.b-scroll { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: stretch; gap: 7px; overflow-y: auto; scrollbar-width: none; }
.b-scroll::-webkit-scrollbar { display: none; }
.b-scroll > * { flex-shrink: 0; }
.b-head { display: flex; align-items: center; gap: 8px; padding-right: 30px; }
.b-avatar { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 800; color: #0D1826; background: linear-gradient(135deg, var(--teal), var(--teal-dark)); }
/* 2-line clamp (2026-08-19), same reasoning as .t-name above — the flipped-card-back name, next
   to a small 26px avatar inside `.b-head` (`display:flex; align-items:center`). `.b-scroll` (this
   row's own container) already handles variable content height via internal scrolling, so a
   taller `.b-head` when a name wraps is safe, no clipping risk. */
.b-name { font-size: 12.5px; font-weight: 800; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.3; }
.b-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.b-cert { display: flex; align-items: flex-start; gap: 7px; background: rgba(22,184,166,0.07); border: 1px solid rgba(22,184,166,0.22); border-radius: 10px; padding: 8px 10px; font-size: 10px; line-height: 1.5; color: var(--white); }
.b-cert small { display: block; font-size: 9.5px; color: var(--muted); margin-top: 1px; }
.b-meta { font-size: 10.5px; color: var(--slate); line-height: 1.5; }
/* "Habla:"/"Speaks:" line (2026-08-14, same round as the language filter above) — same
   typography as .b-meta right above it, ✓ teal only when that specific language is actually
   certified (the same per-language certification flow seeker/perfil.html's own
   .language-pill-certified badge already renders publicly — see buildCardLanguages's own comment,
   api/get-profile.js, for why certifiedByName itself is never part of this public payload). The
   front face is unchanged — this is back-only, same as the mockup's own note-strip decision #3. */
.b-langs { font-size: 10.5px; color: var(--muted); line-height: 1.5; }
.b-langs .bl-cert { color: var(--teal); font-weight: 700; }
.b-status { font-size: 10.5px; color: var(--muted); font-style: italic; }
.b-actions { flex-shrink: 0; padding-top: 4px; display: flex; flex-direction: column; gap: 6px; }
.b-view { display: flex; align-items: center; justify-content: center; font-size: 11.5px; font-weight: 800; border: none; border-radius: 100px; padding: 9px 12px; text-decoration: none; }
.b-conn { display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; color: var(--teal); background: rgba(22,184,166,0.1); border: 1px solid rgba(22,184,166,0.3); border-radius: 100px; padding: 7px 12px; text-decoration: none; }

/* No skeleton/artificial-latency state on purpose: unlike the hub dropdown's live server search,
   every chip-filter/search re-render here reads from the card data already embedded in the page
   (see buildSeekerDirectoryMainHtml's own comment, api/get-profile.js) — there is no network
   round trip to mask, so showing a fake loading state would misrepresent real latency that
   doesn't exist. */

/* ---------- "Save for later" modal shell + toast (2026-08-14, docs/BACKLOG.md /perfiles Clerk
   reversal) — copied verbatim from index.html's/en.html's own inline <style> block (same
   #conn-modal- and .save-cat- and .btn-remove-saved and .save-modal-privacy classnames,
   id-for-id), the one piece of that page's CSS this file didn't already have a copy of (this
   page never needed a modal before Save required one). #dir-toast, not #hub-toast — this isn't
   the hub, kept its own page-appropriate id (directorio.html's/-en.html's own markup,
   directory-carousel.js's showSaveToast). --red hardcoded (#FF6B6B, matches index.html's own
   --red) rather than adding a fourth :root variable to this file's own --slate/--border pair
   above for a value used exactly once.
   REAL BUG FIXED HERE 2026-08-14 (Samuel reported: clicking the save icon never opens the
   category-selection modal, "solo parece reflejar pasivamente si está guardado o no"): the
   ORIGINAL version of this exact comment block wrote its classname list with each name
   immediately followed by a slash and no separating space (e.g. one class ending in a dash-
   asterisk, "conn-modal-", right up against the next token's leading slash). An asterisk sitting
   directly next to a following slash, with nothing between them, is the standard CSS comment
   terminator no matter what English prose surrounds it (CSS comments have no escape sequence),
   so that accidental pair silently closed this whole comment about 6 lines early. Everything
   from `.conn-modal-overlay` below through the END of this file (.conn-modal-box,
   .save-cat-option, #dir-toast, etc. — the entire "Save for later" modal's real styling) then
   got fed to the parser as invalid CSS and discarded: confirmed live via
   document.styleSheets[...].cssRules — the deployed stylesheet's parsed rule count stopped dead
   at `.b-conn` (index 112) and never resumed, so #conn-modal-overlay never actually received a
   fixed position or flex display on production, rendering as a plain block element in normal
   document flow instead — invisible/off-screen rather than a centered modal, exactly matching
   the report. This is why the SIGN-IN overlay (#kreer-custom-auth-overlay, a completely separate
   element styled by its own dynamically-injected <style> in shared-clerk-custom-flow.js) kept
   working fine while this one silently didn't — two different modals, only one of which shared
   this file's broken CSS. Every remaining comment in this file was also re-checked for the same
   asterisk-slash adjacency after this was found; none of the others had it. ---------- */
.conn-modal-overlay { display: none; position: fixed; inset: 0; background: rgba(5,10,18,0.7); backdrop-filter: blur(3px); z-index: 600; align-items: center; justify-content: center; padding: 20px; }
.conn-modal-overlay.open { display: flex; }
.conn-modal-box { position: relative; background: #15223C; border: 1px solid var(--border); border-radius: 16px; padding: 28px; max-width: 460px; width: 100%; max-height: 86vh; overflow-y: auto; }
.conn-modal-close { position: absolute; top: 14px; right: 16px; background: none; border: none; color: var(--muted); font-size: 22px; line-height: 1; cursor: pointer; }
.conn-modal-close:hover { color: var(--white); }
.conn-modal-title { font-size: 17px; font-weight: 800; margin-bottom: 6px; padding-right: 24px; }
.conn-modal-sub { font-size: 12px; color: var(--muted); margin-bottom: 18px; line-height: 1.5; }
.save-cat-option { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.12); border-radius: 12px; padding: 13px 14px; cursor: pointer; color: var(--white); font-family: 'Sora', sans-serif; margin-bottom: 10px; position: relative; }
.save-cat-option:hover { border-color: rgba(22,184,166,0.4); background: rgba(22,184,166,0.06); }
.save-cat-option.is-current { border-color: rgba(22,184,166,0.5); background: rgba(22,184,166,0.08); }
.save-cat-icon { flex-shrink: 0; width: 38px; height: 38px; border-radius: 10px; background: rgba(22,184,166,0.12); color: var(--teal); display: flex; align-items: center; justify-content: center; }
.save-cat-icon svg { width: 18px; height: 18px; }
.save-cat-text { flex: 1; min-width: 0; }
.save-cat-name { font-size: 13.5px; font-weight: 800; }
.save-cat-desc { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.45; }
.save-cat-chev { flex-shrink: 0; color: var(--slate); }
.save-cat-current-pill { position: absolute; top: -9px; right: 12px; font-size: 9.5px; font-weight: 800; color: var(--teal); background: #15223C; border: 1px solid rgba(22,184,166,0.5); border-radius: 100px; padding: 3px 9px; white-space: nowrap; }
.save-modal-privacy { display: flex; align-items: flex-start; gap: 7px; font-size: 10.5px; color: var(--muted); line-height: 1.5; margin-top: 4px; }
.save-modal-privacy svg { flex-shrink: 0; width: 12px; height: 12px; margin-top: 1px; }
.btn-remove-saved { display: block; width: 100%; text-align: center; background: none; border: none; color: #FF6B6B; font-family: 'Sora', sans-serif; font-size: 12px; font-weight: 700; padding: 10px 0 2px; cursor: pointer; }
#dir-toast { display: none; position: fixed; bottom: 100px; left: 50%; transform: translateX(-50%); background: #15223C; border: 1px solid var(--border); border-radius: 10px; padding: 10px 18px; font-size: 12.5px; color: var(--white); z-index: 700; box-shadow: 0 8px 24px rgba(0,0,0,0.4); max-width: calc(100vw - 32px); text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .t-card, .t-flip, .fchip { transition: none !important; }
  .umb-light { animation: none !important; }
}
