/* ==========================================================================
   FlowThrough · round 3 · the result, refined from build r3a (the editorial lean) · public/styles.css
   tokens.css loads before this file, verbatim; every colour, size, duration and easing below is a token or a
   calc of one. The only non-token values are the spec's own numbers (spec.md sections 4, 5 and 9: gaps, the
   tracking of the h2s, the field and panel paddings) and the manifest's focal points (object-position).
   Section order follows the build brief. Reduced motion and no-JS states live in the last block.
   ========================================================================== */

@font-face {
  font-family: "Manrope";
  src: url("/fonts/manrope-var.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

/* ==== base ==== */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-padding-top: calc(var(--bar-h) + var(--header-h) + 16px);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--fw-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

[hidden] { display: none !important; }

h1, h2, p, address, ul { margin: 0; }
address { font-style: normal; }
input, textarea, button { font: inherit; color: inherit; }

/* links on light: --steel-deep, hover --steel-deeper, underline on hover only */
a { color: var(--steel-deep); text-decoration: none; }
a:hover { color: var(--steel-deeper); text-decoration: underline; text-underline-offset: 4px; }

:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* the page's one text edge: the paragraph, the contact panel and the footer start where the media copy does */
.wrap {
  max-width: calc(var(--wrap) + 2 * var(--text-edge));
  margin-inline: auto;
  padding-inline: var(--text-edge);
}

/* skip link: hidden until focused, then a --steel-deep pill top-left, above the preview bar */
.skip {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 1001;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  color: var(--ground);
}
.skip:focus-visible {
  display: inline-flex;
  align-items: center;
  width: auto;
  height: var(--btn-h);
  padding: 0 24px;
  overflow: visible;
  clip-path: none;
  border-radius: var(--r-btn);
  background: var(--steel-deep);
  color: var(--ground);
  font-size: var(--fs-btn);
  font-weight: var(--fw-ui);
  text-decoration: none;
}

/* button on light and on the T2 plate; T1 media turns it white (media-section block) */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--btn-h);
  padding: 0 24px;
  border: 0;
  border-radius: var(--r-btn);
  background: var(--steel-deep);
  color: var(--ground);
  font-family: var(--font-sans);
  font-size: var(--fs-btn);
  font-weight: var(--fw-ui);
  line-height: 1;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color calc(var(--dur-fade) * 2 / 3) var(--ease), color calc(var(--dur-fade) * 2 / 3) var(--ease);
}
.btn:hover { background: var(--steel-deeper); color: var(--ground); text-decoration: none; }
.btn:active { transform: scale(0.98); }

/* ==== header ==== */
/* Sticky under the preview bar and overlaid on the hero (no flow height). Solid at every width from scroll 0:
   --ground at 96 %, the --hair line and ink text. This is spec section 4's fallback for a candidate that fails
   the transparent header even with the wash: over this hero the builder measured 3.1:1 at 768x1024 and the
   round-3 critics measured 2.4 to 4.4:1 at 1280x720, 1366x768, 1536x864, 1920x1080 and 2560x1440, sizes
   verify.py does not sample (decided 2026-09-28). html.scrolled (app.js block 2, after 80 px) keeps the same
   ground and hairline, so the state changes nothing visible; the hero carries no top wash. */
.site-header {
  position: sticky;
  top: var(--bar-h);
  z-index: 100;
  height: var(--header-h);
  margin-bottom: calc(-1 * var(--header-h));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-inline: var(--text-edge);
  background: color-mix(in srgb, var(--ground) 96%, transparent);
  border-bottom: 1px solid var(--hair);
  color: var(--ink);
  transition: background-color var(--dur-fade) var(--ease), border-color var(--dur-fade) var(--ease), color var(--dur-fade) var(--ease);
}
.lockup {
  color: inherit;
  font-size: 20px;
  font-weight: var(--fw-ui);
  line-height: 1;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
}
.lockup:hover { color: inherit; text-decoration: none; }
.site-nav { display: flex; gap: 32px; }
.site-nav a {
  display: inline-block;
  padding-block: 12px;
  color: inherit;
  font-size: var(--fs-nav);
  font-weight: var(--fw-ui);
  line-height: 1;
  letter-spacing: 0.01em;
  text-decoration: none;
  text-decoration-thickness: 1px;
  text-underline-offset: 6px;
  white-space: nowrap;
}
.site-nav a:hover { color: inherit; text-decoration-line: underline; }
@media (max-width: 767px) {
  .lockup { font-size: 18px; }
  .site-nav .nav-wide { display: none; }
}

/* ==== media-section (shared) ==== */
.media-section {
  position: relative;
  overflow: clip;
  isolation: isolate;
  background: var(--ground-soft);
}
.media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.media picture,
.media img {
  display: block;
  width: 100%;
  height: 100%;
}
.media img {
  object-fit: cover;
  object-position: var(--focal, 50% 45%);
}
.scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.media-section[data-treatment="t1"] .scrim { background: var(--scrim-dark); }

/* the text block: the same left edge, width and bottom offset in every media section (rubric L12) */
.copy {
  position: absolute;
  z-index: 2;
  left: var(--text-x);
  bottom: var(--text-bottom);
  width: var(--copy-w);
  padding: var(--copy-pad);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 28px;
}
.copy h1,
.copy h2 {
  color: var(--on-media);
  font-weight: var(--fw-display);
  text-wrap: balance;
}
.copy h1 {
  max-width: var(--measure-display);
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  letter-spacing: var(--ls-hero);
}
.copy h2 {
  max-width: var(--measure-line);
  font-size: var(--fs-line);
  line-height: var(--lh-line);
  letter-spacing: -0.01em;
}
/* T2: ink text on the light plate; from 768 up the plate is the .copy box itself */
.media-section[data-treatment="t2"] .copy {
  background: var(--scrim-light);
  border-radius: var(--r-md);
}
.media-section[data-treatment="t2"] .copy h1,
.media-section[data-treatment="t2"] .copy h2 { color: var(--ink); }
/* T1: white text, the white button, white focus rings */
.media-section[data-treatment="t1"] .btn--on-media { background: var(--ground); color: var(--ink); }
.media-section[data-treatment="t1"] .btn--on-media:hover { background: var(--ground-soft); color: var(--ink); }
.media-section[data-treatment="t1"] .copy :focus-visible { outline-color: var(--focus-on-media); }

/* pause control (spec section 9): only present when a loop plays; bottom-right, its bottom on the button's */
.pause {
  position: absolute;
  z-index: 2;
  right: var(--gutter);
  bottom: calc(var(--text-bottom) + var(--copy-pad));
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1.5px solid color-mix(in srgb, var(--on-media) 70%, transparent);
  border-radius: 50%;
  background: transparent;
  color: var(--on-media);
  cursor: pointer;
}
.pause svg { display: block; width: 16px; height: 16px; }
.pause:focus-visible { outline-color: var(--focus-on-media); }
.media-section[data-treatment="t2"] .pause {
  border-color: color-mix(in srgb, var(--ink) 60%, transparent);
  color: var(--ink);
}
.media-section[data-treatment="t2"] .pause:focus-visible { outline-color: var(--focus); }

@media (max-width: 767px) {
  .copy { gap: 20px; }
  /* the T2 plate runs edge to edge and flush with the section's bottom; the text still ends --text-bottom above it */
  .media-section[data-treatment="t2"] .copy {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    padding: var(--plate-pad-sm) var(--gutter) var(--text-bottom);
    border-radius: 0;
  }
}

/* ==== hero ==== */
#hero { height: var(--hero-h); }
/* focal point from the manifest (hero: 0.62, 0.45); the Ken Burns zooms towards it */
#hero .media img { --focal: 62% 45%; transform-origin: 62% 45%; }
/* no top wash: the header is solid at every width (the header block), so the hero's .scrim carries only its
   treatment's gradient from the shared block (nothing on T2, --scrim-dark on T1) */

/* ==== bloques ==== */
#bloque-1,
#bloque-2,
#bloque-3 { height: var(--block-h); }
/* focal points from the manifest */
#bloque-1 .media img { --focal: 48% 55%; }
#bloque-2 .media img { --focal: 50% 55%; }
#bloque-3 .media img { --focal: 55% 50%; }
@media (max-width: 767px) {
  #bloque-1,
  #bloque-2,
  #bloque-3 { height: var(--block-h-sm); }
}

/* ==== quienes-somos ==== */
.para-section {
  padding-block: var(--sp-section);
  background: var(--ground);
}
.para-section .wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}
.label {
  color: var(--olive-text);
  font-size: var(--fs-label);
  font-weight: var(--fw-ui);
  line-height: 1.4;
  letter-spacing: 0.02em;
}
.para {
  max-width: var(--measure);
  font-size: var(--fs-para);
  line-height: var(--lh-para);
  text-wrap: pretty;
}
.para-section .btn { margin-top: 16px; }

/* ==== ambitos ==== */
.marquee-section {
  position: relative;
  background: var(--sage-tint);
  color: var(--olive-text);
}
.marquee {
  display: flex;
  align-items: center;
  min-height: 72px;
  overflow: hidden;
}
/* a track is never narrower than the band: the six items measure about 1366 px, so from about 1400 px up the
   band's right end went empty for the last third of each pass (592 px at 1920; craft critic, 2026-09-28).
   min-width 100 % with space-around spreads the items over the band and keeps translateX(-100%) seamless: the
   spacing at the join of the two tracks equals the spacing inside one. */
.track {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: 40px;
  min-width: 100%;
  padding: 0 40px 0 0;
  list-style: none;
  font-size: var(--fs-marquee);
  font-weight: var(--fw-body);
  line-height: 1;
  letter-spacing: 0.02em;
  white-space: nowrap;
  animation: marquee var(--marquee-dur) linear infinite;
}
.track li {
  display: flex;
  align-items: center;
  gap: 40px;
}
.track li::before {
  content: "";
  flex: none;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--olive);
}
.marquee:hover .track,
.marquee.is-paused .track,
.marquee.is-offscreen .track { animation-play-state: paused; }
/* the toggle: a 32 px circle at the band's right end on a --sage-tint ground, so the items pass under it */
.marquee-toggle {
  position: absolute;
  top: 50%;
  right: var(--gutter);
  z-index: 1;
  width: 32px;
  height: 32px;
  margin: 0;
  padding: 0;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  background: var(--sage-tint);
  color: var(--olive-text);
  cursor: pointer;
}
.marquee-toggle::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 50%;
}
.marquee-toggle svg { display: block; width: 12px; height: 12px; }
.marquee:not(.is-paused) + .marquee-toggle .glyph-play,
.marquee.is-paused + .marquee-toggle .glyph-pause { display: none; }
@media (max-width: 767px) {
  .marquee { min-height: 64px; }
}

/* ==== contacto ==== */
.contact-section {
  padding-block: var(--sp-section);
  background: var(--ground);
}
/* the panel obeys the plate geometry: its box starts at --text-x and its inner text on --text-edge */
.contact-panel {
  display: grid;
  gap: 40px 64px;
  margin-inline: calc(-1 * var(--copy-pad));
  padding: 56px var(--copy-pad);
  background: var(--steel-wash);
  border-radius: var(--r-md);
}
.contact-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
}
.contact-text h2 {
  max-width: 18ch;
  font-size: var(--fs-h2-soft);
  font-weight: var(--fw-display);
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.contact-text p { max-width: var(--measure); text-wrap: pretty; }
.contact-text address { margin-top: 12px; }
.contact-form {
  display: grid;
  gap: 20px;
}
.field { display: grid; gap: 8px; }
.field label {
  font-size: var(--fs-nav);
  font-weight: var(--fw-ui);
  line-height: 1.4;
}
.contact-form input:not([type="checkbox"]),
.contact-form textarea {
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  border: 1px solid var(--muted); /* not --grey at 60 % (1.4 to 1.6:1): --muted gives 4.8:1 on --steel-wash and
                                     5.5:1 on white, the 3:1 of WCAG 1.4.11 (decided 2026-09-28); focus stays --steel-deep */
  border-radius: var(--r-input);
  background: var(--ground);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.contact-form textarea {
  min-height: 160px;
  padding-block: 12px;
  resize: vertical;
}
.contact-form input:focus-visible,
.contact-form textarea:focus-visible { border-color: var(--steel-deep); }
/* a field the check marked (app.js block 7 sets aria-invalid): its edge turns --steel-deeper and doubles to 2 px, so
   the mark is a change of weight, not of colour alone; focus keeps its own ring */
.contact-form input[aria-invalid="true"]:not([type="checkbox"]),
.contact-form textarea[aria-invalid="true"] {
  border-color: var(--steel-deeper);
  box-shadow: inset 0 0 0 1px var(--steel-deeper);
}
.check {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.check input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 4px 0 0;
  accent-color: var(--steel-deep);
}
/* the privacy box, marked: a 2 px ring tight to the box, so it never reads as the focus ring (3 px, offset 3) */
.check input[aria-invalid="true"] {
  border-radius: 4px;
  box-shadow: 0 0 0 2px var(--steel-deeper);
}
/* the first-layer data-protection notice under the box; takes no room while it is empty */
.form-legal {
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--muted);
}
.form-legal:empty { display: none; }
/* the honeypot (src/contact.js): out of sight, out of the tab order and, by aria-hidden in the markup, out of the
   accessibility tree; a person never fills it */
.field-trap {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* the Turnstile widget (app.js block 7): the slot shows when its script starts to load and holds the widget's 65 px,
   so the button does not jump when the iframe arrives; size "flexible" fills the column */
.turnstile-slot { min-height: 65px; }
.contact-form .btn { justify-self: start; }
.form-note {
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--muted);
}
/* the states (app.js block 7): sending fades the button and takes it out of reach until the Worker answers; the
   note carries the words of every state, so none relies on colour alone */
.contact-form[data-state="sending"] .btn { opacity: 0.6; pointer-events: none; }
.contact-form[data-state="sent"] .form-note { color: var(--olive-text); }
.contact-form[data-state="invalid"] .form-note,
.contact-form[data-state="error"] .form-note { color: var(--ink); }
@media (min-width: 1024px) {
  .contact-panel { grid-template-columns: 5fr 7fr; align-items: start; }
}
@media (max-width: 767px) {
  .contact-panel {
    margin-inline: calc(-1 * var(--gutter));
    padding: 28px var(--gutter);
    border-radius: 0;
  }
}

/* ==== footer ==== */
.site-footer {
  padding-block: 48px 40px;
  background: var(--ground-soft);
  border-top: 1px solid var(--hair);
  color: var(--ink);
  font-size: var(--fs-small);
  line-height: 1.5;
}
.footer-grid {
  display: grid;
  gap: 32px;
}
.footer-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}
.site-footer .lockup--footer {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
}
.mark {
  display: block;
  width: 24px;
  height: 24px;
  color: var(--steel-deep);
}
.footer-legal {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 20px;
}
.copyright {
  margin-top: 32px;
  color: var(--muted);
}
@media (min-width: 1024px) {
  .footer-grid { grid-template-columns: 1fr auto 1fr; align-items: start; }
  .footer-legal { justify-self: end; }
}

/* ==== motion ==== */
/* Only imagery moves, plus soft reveals: the Ken Burns on the hero still, the text reveals, the marquee drift.
   Everything here is gated by html.js, so the page is complete at rest without it. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
html.js .reveal > * {
  opacity: 0;
  translate: 0 12px;
  transition: opacity var(--dur-reveal) var(--ease), translate var(--dur-reveal) var(--ease),
              background-color calc(var(--dur-fade) * 2 / 3) var(--ease), color calc(var(--dur-fade) * 2 / 3) var(--ease);
}
html.js .reveal > :nth-child(2) { transition-delay: calc(var(--dur-reveal) / 8), calc(var(--dur-reveal) / 8), 0s, 0s; }
html.js .reveal > :nth-child(3) { transition-delay: calc(var(--dur-reveal) / 4), calc(var(--dur-reveal) / 4), 0s, 0s; }
html.js .reveal.is-in > * {
  opacity: 1;
  translate: none;
}
html.js #hero .media img { animation: ken-burns var(--kb-dur) var(--ease) forwards; }
@keyframes ken-burns {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}
@keyframes marquee {
  to { transform: translateX(-100%); }
}
/* An h1 already painted is never hidden again. When app.js lands after first paint (a slow network) the page has
   shown its no-JS state, the hero text at full opacity; the html.js reveal rule above would then dip it to 0 and
   fade it back (a blink). app.js block 2 adds html.painted next to html.js when a paint entry already exists, and
   this rule keeps the hero copy visible with only the button's hover fade. */
html.js.painted #hero .reveal > * {
  opacity: 1;
  translate: none;
  transition: background-color calc(var(--dur-fade) * 2 / 3) var(--ease), color calc(var(--dur-fade) * 2 / 3) var(--ease);
}

/* ==== reduced motion and no-js ==== */
/* Reduced motion: no Ken Burns, no reveals waiting, no drift; the marquee is a centred wrapped list with a
   16 px gap and no dots, its duplicate hidden, the band growing to fit; the toggle never shows. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js .reveal > * {
    opacity: 1;
    translate: none;
    transition: none;
  }
  html.js #hero .media img { animation: none; }
  .marquee { justify-content: center; }
  .track {
    flex: 1 1 auto;
    flex-wrap: wrap;
    justify-content: center;
    gap: 16px;
    padding: 20px var(--gutter);
    line-height: 1.4;
    animation: none;
  }
  .track li { gap: 0; }
  .track li::before { content: none; }
  .track[aria-hidden="true"] { display: none; }
  .marquee-toggle { display: none; }
}
/* Without JS: the header stays solid (its default), no reveal rule applies, the still is static, and the
   marquee is the same wrapped list with its toggle hidden (the markup carries hidden). */
html:not(.js) .marquee { justify-content: center; }
html:not(.js) .track {
  flex: 1 1 auto;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
  padding: 20px var(--gutter);
  line-height: 1.4;
  animation: none;
}
html:not(.js) .track li { gap: 0; }
html:not(.js) .track li::before { content: none; }
html:not(.js) .track[aria-hidden="true"] { display: none; }
html:not(.js) .marquee-toggle { display: none; }
