/* Camino Systems, "Tampa waterfront" design system (2026-09-19).
   Founder-pinned from a mock: an off-white ground in hard Florida light, soft palm
   shadows at the corners, one royal blue, Inter Tight for everything, and one large
   rounded photograph of the bay as the first thing on the page. Light only. */

@font-face {
  font-family: 'Inter Tight';
  src: url('/fonts/inter-tight.woff2') format('woff2');
  font-weight: 100 900; font-style: normal; font-display: swap;
}

:root {
  --bg: #F7F4EF;
  --bg-2: #EFEBE4;
  --card: #FFFFFF;
  --ink: #0B1220;
  --soft: #5A6069;
  --line: #E3DFD6;
  --line-strong: #CFCAC0;
  --blue: #0B48E0;
  --blue-deep: #0838B8;
  /* the lit state of the royal blue, used only by the moving highlight on the
     automation renders (src/components/work/AutoFlow.astro) */
  --blue-lit: #7FB2FF;
  --on-photo: #FFFFFF;

  --font: 'Inter Tight', system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;

  --fs-display: clamp(2.75rem, 4.6vw, 4.4rem);
  --fs-h2: clamp(2rem, 3.8vw, 3.375rem);
  --fs-h3: clamp(1.375rem, 1.8vw, 1.625rem);
  --fs-lead: clamp(1.125rem, 1.35vw, 1.25rem);
  --fs-body: 1.0625rem;
  --fs-ui: .9375rem;
  --fs-small: .875rem;
  --fs-micro: .6875rem;

  --r-card: 26px;
  --r-btn: 999px;
  --r-sm: 12px;
  --r-inset: 14px;

  --shadow-card: 0 30px 60px -30px rgba(11, 18, 32, .38), 0 2px 6px rgba(11, 18, 32, .05);
  --shadow-soft: 0 14px 34px -22px rgba(11, 18, 32, .3);
  --shadow-text: 0 1px 8px rgba(0, 0, 0, .45);
  --shadow-inset: 0 20px 40px -18px rgba(11, 18, 32, .6);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  --wrap: 82rem;
  --gutter: 2.5rem;
  --shadow-a-ratio: 900 / 831;
  --shadow-b-ratio: 640 / 1161;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font); font-size: var(--fs-body); line-height: 1.55;
  font-feature-settings: 'cv11', 'ss01';
  -webkit-font-smoothing: antialiased;
  position: relative; overflow-x: clip;
}
img, video { max-width: 100%; display: block; height: auto; }
a { color: var(--blue-deep); text-underline-offset: 3px; }
p { margin: 0; max-width: 68ch; }
p + p { margin-top: 1rem; }
h1, h2, h3 { margin: 0; font-weight: 700; letter-spacing: -.035em; text-wrap: balance; }
h1 { font-size: var(--fs-display); line-height: .96; }
h2 { font-size: var(--fs-h2); line-height: 1; }
h3 { font-size: var(--fs-h3); line-height: 1.12; letter-spacing: -.025em; }
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); position: relative; z-index: 1; }
.lead { font-size: var(--fs-lead); line-height: 1.5; color: var(--ink); max-width: 46ch; }
.sub { color: var(--soft); font-size: var(--fs-lead); line-height: 1.5; max-width: 52ch; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip { position: absolute; left: 1rem; top: -4rem; background: var(--ink); color: #fff; padding: .6rem 1rem; border-radius: var(--r-sm); z-index: 60; }
.skip:focus { top: 1rem; }
:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; border-radius: var(--r-sm); }

/* the small tracked line used across the site: the header tag, the strip, the
   photo corners. Never sits above a heading. */
.micro {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: .26em; text-transform: uppercase;
  line-height: 1.7; color: var(--soft);
}
/* the short rule: aligned to the first line's caps, so a wrapped line never leaves it floating */
.micro-rule { display: inline-flex; align-items: flex-start; gap: .9rem; }
.micro-rule::before { content: ''; flex: none; width: 2.25rem; height: 1px; margin-top: .8em; background: currentColor; opacity: .6; }

/* ---------------- palm shadows ----------------
   Real cast shadows, not silhouettes: a photograph of palm fronds shadowing an
   off-white wall (Higgsfield, gpt_image_2_5) converted to two alpha layers in
   ink, so the feathering and the falloff are the photograph's own. Fixed at
   the corners under everything, the way the founder's mock has them; the
   bottom-left one is the top-left layer flipped. The sway animates transform
   only. */
.palm { position: fixed; z-index: 0; pointer-events: none; user-select: none; opacity: .72; background: 50% 50% / 100% auto no-repeat; }
.palm-a, .palm-b { background-image: url('/shadow-a.webp'); aspect-ratio: var(--shadow-a-ratio); }
.palm-c { background-image: url('/shadow-b.webp'); aspect-ratio: var(--shadow-b-ratio); }
.palm-a { left: -6vw; top: -8vw; width: 54vw; max-width: 900px; }
.palm-b { left: -14vw; bottom: -16vw; width: 46vw; max-width: 760px; transform: scaleY(-1) rotate(6deg); }
.palm-c { right: -6vw; top: 4vw; width: 34vw; max-width: 560px; }
/* No palm shadows on a phone (founder, 2026-09-23). They were the largest thing
   painted above the fold, which made a decorative background-image the LCP element on
   every page, and they crowd a 390px screen that has no room to spare. Hiding them
   here also drops their 77KB off the mobile critical path entirely; the preload in
   Head.astro carries a matching `media` so a phone never fetches the file at all. */
@media (max-width: 48rem) {
  .palm { display: none; }
}

/* The questions buyers ask, answered in the open. Visible text rather than an
   accordion: a reader scanning for the cost question should not have to guess which
   row hides it, and the FAQPage markup requires the answer to be on the page. */
.faq { display: grid; gap: 2rem 2.5rem; }
@media (min-width: 64rem) {
  .faq { grid-template-columns: 1fr 1fr; align-items: start; }
}
.faq-item h3 { font-size: var(--fs-lead); font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.faq-item p { margin-top: .6rem; color: var(--soft); max-width: 60ch; }

/* ---------------- the actions pinned to a phone screen ----------------
   src/components/StickyActions.astro. Phones only: above 48rem the header's own button
   is still in reach. The body gains matching bottom room so the bar never covers the
   last line of the footer, and the bar slides away while the page's closing card is on
   screen so the visitor is not asked the same thing twice in one view. */
.fab { display: none; }
.fab-actions { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; justify-items: end; }
.fab-action {
  display: inline-flex; align-items: center; gap: .6rem; min-height: 48px;
  padding: 0 .35rem 0 1rem; border-radius: 999px; text-decoration: none;
  font-size: var(--fs-ui); font-weight: 600; color: var(--ink);
  /* Shadow only, no border. These float above a dimmed page, so the shadow is what
     lifts them; adding a hairline as well declares the same elevation twice. */
  background: var(--card);
  box-shadow: 0 10px 24px -12px rgba(11, 18, 32, .55);
}
.fab-action .fab-icon {
  display: grid; place-items: center; width: 38px; height: 38px; flex: none;
  border-radius: 50%; background: var(--bg-2);
}
.fab-action.is-primary { background: var(--blue); color: #FFFFFF; border-color: var(--blue); }
.fab-action.is-primary .fab-icon { background: rgba(255, 255, 255, .18); }
.fab-action svg { width: 16px; height: 16px; }
.fab-toggle {
  display: inline-flex; align-items: center; gap: .55rem; justify-self: end;
  min-height: 56px; padding: 0 .5rem 0 1.25rem; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: var(--fs-ui); font-weight: 600;
  color: #FFFFFF; background: var(--blue); border: 0;
  box-shadow: 0 14px 30px -12px rgba(8, 56, 184, .7);
}
.fab-toggle-glyph { position: relative; display: grid; place-items: center; width: 40px; height: 40px; flex: none; border-radius: 50%; background: rgba(255, 255, 255, .18); }
.fab-toggle-glyph svg { width: 17px; height: 17px; grid-area: 1 / 1; }
.fab-glyph-close { opacity: 0; }

/* ---------------- the pinned contact control ----------------
   Phones only. Above 48rem the header's own button is in reach and a control floating
   over a laptop page is clutter, and `sms:` does nothing on most desktops anyway.

   Closed, this is one 56px target in the bottom-right thumb arc. Open, it is three.
   The three actions are in the DOM at all times so that without JavaScript they simply
   render as a stack: nothing is ever hidden behind a control that cannot be operated.
   `.js` is what collapses them, set before first paint by the inline boot script. */
@media (max-width: 48rem) {
  .fab {
    display: grid; gap: .6rem; justify-items: end;
    position: fixed; right: var(--gutter); z-index: 20;
    bottom: calc(1rem + env(safe-area-inset-bottom));
  }
  /* The scrim. Without it the three pills sit straight on top of the page copy and
     read as clutter rather than as a menu: measured against the hero's caps line,
     white pills on an off-white page have almost nothing to separate them. Dimming
     what is behind makes the stack read as a layer. It is a pseudo-element so the
     markup stays three links and a button, and it carries pointer-events only while
     open so it never swallows a tap on the page underneath. */
  .fab::before {
    content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background: rgba(11, 18, 32, .34); opacity: 0;
  }
  .js .fab[data-open]::before { opacity: 1; pointer-events: auto; }
  .js .fab-actions {
    /* `visibility` rather than `display`, so the actions can transition, and so they
       leave the tab order while closed. `display: none` cannot be transitioned and
       would make the stagger below impossible. */
    visibility: hidden; opacity: 0;
  }
  .js .fab[data-open] .fab-actions { visibility: visible; opacity: 1; }
  .js .fab-action {
    opacity: 0; transform: translateY(12px) scale(.94); transform-origin: 100% 50%;
  }
  .js .fab[data-open] .fab-action { opacity: 1; transform: none; }
  .fab.is-hidden { opacity: 0; transform: translateY(120%); pointer-events: none; }

  @media (prefers-reduced-motion: no-preference) {
    .fab { transition: transform 260ms var(--ease-out), opacity 200ms ease; }
    .js .fab-actions { transition: opacity 160ms ease, visibility 0s linear 220ms; }
    .js .fab[data-open] .fab-actions { transition: opacity 160ms ease, visibility 0s; }
    /* The stagger is what makes it read as opening rather than appearing. Bottom-up
       on open, because the eye is already at the control and the nearest action should
       arrive first. The delays reverse on close via the closing selector below. */
    .js .fab-action { transition: opacity 200ms ease, transform 260ms var(--ease-out); }
    .js .fab[data-open] li:nth-child(3) .fab-action { transition-delay: 0ms; }
    .js .fab[data-open] li:nth-child(2) .fab-action { transition-delay: 45ms; }
    .js .fab[data-open] li:nth-child(1) .fab-action { transition-delay: 90ms; }
    .fab-toggle-glyph svg { transition: opacity 160ms ease, transform 260ms var(--ease-out); }
    .fab::before { transition: opacity 220ms ease; }
  }
  .js .fab[data-open] .fab-glyph-open { opacity: 0; transform: rotate(-90deg); }
  .js .fab[data-open] .fab-glyph-close { opacity: 1; }
  .js .fab[data-open] .fab-toggle-label { display: none; }
  .js .fab[data-open] .fab-toggle { padding-right: .5rem; padding-left: .5rem; }
}

/* ---------------- the ways to reach us ----------------
   What /contact shows while the form is parked. Three routes as large targets rather
   than a line of links, because this page exists to be acted on. */
.contact-routes { display: grid; gap: 1rem; align-content: start; }
.contact-route {
  display: grid; gap: .3rem; text-decoration: none; color: var(--ink);
  background: var(--card); border-radius: var(--r-card); box-shadow: var(--shadow-soft);
  padding: 1.5rem 1.75rem; transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out);
}
.contact-route:hover { transform: translateY(-2px); box-shadow: var(--shadow-card); }
.contact-route-label { color: var(--soft); }
.contact-route-value {
  font-size: var(--fs-lead); font-weight: 700; letter-spacing: -.02em;
  /* rmontufar@caminosystems.co has no space in it, so without this it is one
     unbreakable token that widens its card instead of wrapping. `anywhere` is the
     one that also stops it forcing the grid track wider; `break-word` does not.
     Measured 2026-09-22 in a real narrow viewport: --fs-lead clamps to 18px at
     every phone width, so the address is 259px and sits on one line from 360px up
     (255px of box at 375px, 270px at 390px). At 320px it wraps to two lines, which
     is what this rule is for. An earlier pass sized this from a 1512px window,
     where the same token gives 20px, and added padding and font-size overrides
     that fixed nothing; they were measured away rather than left in. */
  overflow-wrap: anywhere;
}
.contact-route-note { font-size: var(--fs-small); color: var(--soft); }
.contact-route.is-primary { background: var(--blue); color: #FFFFFF; }
.contact-route.is-primary .contact-route-label,
.contact-route.is-primary .contact-route-note { color: rgba(255, 255, 255, .82); }

/* ---------------- the contact form ----------------
   Four fields. Every field a person has to think about is a field some of them stop
   at, so this asks for a name, an address to reply to, an optional number, and the
   problem. `.field-trap` is a honeypot: off screen, out of the tab order, hidden from
   assistive technology, and never shown to a person. */
.contact-form {
  background: var(--card); border-radius: var(--r-card); box-shadow: var(--shadow-soft);
  padding: 2.25rem; display: grid; gap: 1.25rem; align-content: start;
}
.field { display: grid; gap: .45rem; }
.field label { font-size: var(--fs-small); font-weight: 600; color: var(--ink); }
.field-note { font-weight: 400; color: var(--soft); }
.field input, .field textarea {
  font: inherit; font-size: var(--fs-body); color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  padding: .7rem .85rem; width: 100%; min-height: 48px;
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:focus-visible, .field textarea:focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; }
.field-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.contact-form .btn { justify-self: start; }
.form-note { font-size: var(--fs-small); color: var(--soft); max-width: 46ch; }
.form-status { font-size: var(--fs-small); font-weight: 600; }
.form-status:empty { display: none; }
.form-status.is-bad { color: var(--blue-deep); }
.contact-facts { margin-top: 2rem; }
@media (max-width: 64rem) {
  .contact-form { padding: 1.5rem; }
}

/* ---------------- header ----------------
   Static, not fixed. The mock's header is part of the page, not a bar over it. */
.site-head { position: relative; z-index: 5; padding: 1.75rem 0 0; }
.nav { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 2rem; }
.brand {
  color: var(--ink); text-decoration: none; display: inline-grid; justify-items: start;
  line-height: 1; min-height: 44px; align-content: center;
}
.brand-top { font-weight: 800; font-size: var(--fs-lead); letter-spacing: .3em; text-transform: uppercase; }
.brand-sub { font-weight: 500; font-size: var(--fs-micro); letter-spacing: .5em; text-transform: uppercase; margin: .45rem 0 0 .15rem; color: var(--ink); }
.nav-links { display: flex; align-items: center; gap: 2.5rem; justify-content: center; }
.nav-links a {
  color: var(--ink); text-decoration: none; font-size: var(--fs-ui); font-weight: 500;
  display: inline-flex; align-items: center; min-height: 44px; position: relative;
}
.nav-links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 8px; height: 2px;
  background: var(--blue); transform: scaleX(0); transform-origin: left;
  transition: transform 220ms var(--ease-out);
}
.nav-links a:hover::after, .nav-links a[aria-current="page"]::after { transform: scaleX(1); }
.nav-links a.nav-cta { display: none; }
.nav-end { display: flex; align-items: center; gap: 2.25rem; }
/* Tap to call, in the header on every screen. Every Tampa competitor puts a phone
   above the fold and this site had none; for a local service buyer on a phone it is
   the shortest path there is. The digits fold away on the narrowest screens so the
   brand and the menu still fit, and the icon keeps its accessible name. */
.nav-phone {
  display: inline-flex; align-items: center; gap: .5rem; min-height: 44px;
  color: var(--ink); text-decoration: none; font-size: var(--fs-ui); font-weight: 600;
  white-space: nowrap;
}
.nav-phone svg { width: 17px; height: 17px; flex: none; }
.nav-phone:hover .nav-phone-num { text-decoration: underline; text-underline-offset: 3px; }
.nav-tag { display: inline-grid; gap: .15rem; text-align: left; }
.nav-tag .micro { color: var(--ink); line-height: 1.2; white-space: nowrap; }
.nav-toggle {
  display: none; align-items: center; gap: .5rem; min-height: 44px; min-width: 44px;
  background: var(--card); border: 1px solid var(--line-strong); border-radius: var(--r-btn);
  padding: .5rem 1rem; font: inherit; font-weight: 600; font-size: var(--fs-small);
  color: var(--ink); cursor: pointer;
}
.nav-toggle .bar { transition: transform 220ms var(--ease-out); transform-origin: 50% 50%; }
.nav-open .nav-toggle .bar-1 { transform: translateY(3.5px) rotate(45deg); }
.nav-open .nav-toggle .bar-2 { transform: translateY(-3.5px) rotate(-45deg); }

@media (max-width: 64rem) {
  .nav { grid-template-columns: auto auto; justify-content: space-between; }
  .nav-tag { display: none; }
  .nav-end { gap: 1rem; }
  .nav-toggle { display: inline-flex; }
  .nav-end { grid-row: 1; grid-column: 2; }
  .nav-links {
    grid-row: 2; grid-column: 1 / -1; display: none; flex-direction: column; align-items: stretch; gap: 0;
    background: var(--card); border-radius: var(--r-card);
    box-shadow: var(--shadow-card); padding: .5rem 1.25rem 1.25rem; margin-top: 1rem;
  }
  .nav-open .nav-links { display: flex; }
  @media (prefers-reduced-motion: no-preference) {
    .nav-open .nav-links { animation: menu-in 260ms var(--ease-out); }
  }
  .nav-links a { font-size: var(--fs-lead); font-weight: 600; padding: .8rem 0; min-height: 56px; border-bottom: 1px solid var(--line); }
  .nav-links a::after { display: none; }
  .nav-links a.nav-cta {
    display: inline-flex; gap: .6rem; margin-top: 1rem; justify-content: center; border-bottom: 0;
    background: var(--blue); color: #FFFFFF; padding: 1rem; border-radius: var(--r-btn); min-height: 52px;
  }
  .nav-end .btn { display: none; }
  html:not(.js) .nav-toggle { display: none; }
  html:not(.js) .nav-links { display: flex; }
}
@media (max-width: 64rem) and (min-width: 30rem) {
  .nav-end .btn { display: inline-flex; }
}
/* On a phone the header has to fit the brand, a tappable number and the menu. The
   number wins over the word "Menu": a local buyer reading this on a phone is one tap
   from calling, and the hamburger is understood without its label. The label stays in
   the accessibility tree, and script.js keeps swapping it to "Close", so a screen
   reader still hears the right thing. */
@media (max-width: 26rem) {
  /* 2.5rem of gutter each side takes a fifth of a 390px screen, and the header needs
     that room for the brand, the number and the menu. Measured at 390px: 291px of
     content against 310px of room at the old gutter, and 4px of slack on a 375px
     phone. 2rem restores a comfortable margin without changing how the page reads. */
  :root { --gutter: 2rem; }
  .nav-end { gap: .6rem; }
  .nav-toggle { padding: .5rem .7rem; }
  .nav-toggle-label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .brand-sub { letter-spacing: .36em; }
  .nav-phone { font-size: var(--fs-small); }
  /* The 2rem grid gap between the brand and the actions is a tenth of a 320px
     screen. See the reflow note below for the arithmetic it is part of. */
  .nav { gap: .75rem; }
}

/* ---------------- reflow at 320px ----------------
   WCAG 2.2 AA SC 1.4.10 Reflow asks for no horizontal scrolling at 320px CSS
   width, and we sell AA reviews, so the site meets it. It did not: measured on
   2026-09-22 at 320px, the header wanted 378px. 32px of gutter each side, a
   106px brand, a 32px grid gap and a 176px actions group, against 320px of room.
   Every page scrolled sideways, not just the narrow ones.

   The 2rem gap above takes it to 358px, which clears a 375px phone with 17px to
   spare. Below 23rem the number leaves the header as well, because 358px does
   not fit 368px and the brand is the one thing that cannot go. Nothing is lost
   by that: the pinned bar at the bottom of the screen carries Call, Text and
   Book at every width under 48rem, and it steps aside only when the page's own
   closing card, which repeats the number, is already on screen. */
@media (max-width: 23rem) {
  :root { --gutter: 1.5rem; }
  .nav-phone { display: none; }
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

/* ---------------- buttons ---------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .7rem;
  padding: .95rem 1.6rem; border-radius: var(--r-btn);
  font-weight: 600; font-size: var(--fs-ui); text-decoration: none; letter-spacing: -.005em;
  min-height: 50px; cursor: pointer; white-space: nowrap;
  transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out), background 180ms ease, border-color 180ms ease;
}
.arrow { display: inline-flex; width: 1em; height: 1em; }
.arrow svg { width: 100%; height: 100%; display: block; }
.btn .arrow { transition: transform 200ms var(--ease-out); }
.btn:hover .arrow { transform: translateX(4px); }
.btn-primary { background: var(--blue); color: #FFFFFF; box-shadow: 0 14px 28px -14px rgba(11, 72, 224, .7); }
.btn-primary:hover { transform: translateY(-1px); background: var(--blue-deep); box-shadow: 0 18px 32px -14px rgba(11, 72, 224, .75); }
.btn-primary:active { transform: translateY(0); }
.btn-ghost { background: var(--card); color: var(--ink); border: 1px solid var(--line-strong); }
.btn-ghost:hover { transform: translateY(-1px); border-color: var(--ink); }
.btn-big { padding: 1.1rem 1.9rem; font-size: var(--fs-body); min-height: 56px; }
.btns { display: flex; gap: 1rem 1.75rem; flex-wrap: wrap; margin-top: 2.25rem; align-items: center; }
.text-link { font-weight: 600; color: var(--blue-deep); text-decoration: none; display: inline-flex; align-items: center; gap: .5rem; }
.text-link .arrow { transition: transform 200ms var(--ease-out); }
.text-link:hover .arrow { transform: translateX(4px); }
.text-link:hover { text-decoration: underline; }

/* ---------------- hero ----------------
   Copy at five columns, the photograph at seven. The photograph is the page's
   thesis: a Tampa morning, real work on the seawall, the water moving. */
.hero { padding: 3.5rem 0 2.5rem; }
.hero-grid {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 3.5rem; align-items: center;
}
.hero-copy h1 { max-width: 15ch; }
.hero-copy .lead { margin-top: 1.5rem; }
/* Two ways out of the hero: book the call, or look at the work first. They sit on
   one row and the line about who you work with sits under them, so the row wraps as
   a pair rather than leaving one button stranded beside a label. */
/* Two ways out of the hero: book the call, or look at the work first. */
.hero-actions { margin-top: 2.25rem; }
.hero-buttons { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.hero-meta { margin-top: 2.5rem; }

.hero-card {
  position: relative; border-radius: var(--r-card); overflow: hidden;
  background: var(--bg-2); box-shadow: var(--shadow-card);
  aspect-ratio: 3 / 2;
}
.hero-card > img, .hero-card > picture > img, .hero-card video { width: 100%; height: 100%; object-fit: cover; }
.hero-card > img, .hero-card > picture > img { position: absolute; inset: 0; }
.hero-card video { position: absolute; inset: 0; opacity: 0; transition: opacity 600ms ease; }
.hero-card.is-playing video { opacity: 1; }
/* A scrim at the top and bottom of every photo card, so the labels read at contrast
   over sky and sunlit concrete.
   MEASURED, NOT ESTIMATED. The labels sit 1.5rem down, which on a 457px card is 5% to
   12% of the way in. The old ramp (.5 at the top, nothing by 30%) had faded to about
   .3 by the time it reached the text, and the composited background behind the white
   11px caps measured 3.19:1 against the 4.5:1 that WCAG 2.2 AA requires at that size.
   This holds the scrim near .5 across the band the labels actually occupy. Re-measure
   from a screenshot, not from the gradient, if the hero photograph changes. */
.media-card:not(.is-light)::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to bottom, rgba(11, 18, 32, .72) 0%, rgba(11, 18, 32, .48) 14%, rgba(11, 18, 32, 0) 36%, rgba(11, 18, 32, 0) 66%, rgba(11, 18, 32, .58) 100%);
}
/* A card with no labels has nothing to hold at contrast, so it keeps its own picture:
   the OmniMarket poster on the home page was being darkened for no reader's benefit.
   Written as a removal rather than as `:has(.corner)` on the rule above on purpose --
   where :has() is not supported the scrim simply stays, which is the safe direction. */
.media-card:not(.is-light):not(:has(.corner))::after { background: none; }
.hero-card .corner {
  position: absolute; z-index: 2; color: var(--on-photo); text-shadow: var(--shadow-text);
  display: grid; gap: .15rem;
}
.hero-card .corner .micro { color: inherit; line-height: 1.55; }
.corner-tl { left: 2.25rem; top: 2.25rem; }
.corner-tr { right: 2.25rem; top: 2.25rem; text-align: right; justify-items: end; }
.corner-bl { left: 2.25rem; bottom: 2rem; }
.corner-rule { display: block; width: 2.25rem; height: 1px; background: currentColor; opacity: .8; margin-top: .4rem; }

@media (max-width: 64rem) {
  .hero { padding: 2.5rem 0 1.5rem; }
  .hero-grid { grid-template-columns: 1fr; gap: 2.25rem; }
  .hero-copy h1 { max-width: 14ch; }
  .hero-card { aspect-ratio: 4 / 3; }
  .corner-tl, .corner-tr { top: 1.25rem; }
  .corner-tl, .corner-bl { left: 1.25rem; }
  .corner-tr { right: 1.25rem; }
  .corner-bl { bottom: 1.15rem; }
  .hero-card .corner-tr, .media-card .corner-tr { display: none; }
}

/* ---------------- the strip ----------------
   The thin line under the hero from the mock: a place, the three lines of work,
   a rule, one fact. */
.strip { padding: 1.5rem 0 0; }
.strip-row {
  display: grid; grid-template-columns: auto auto 1fr auto; align-items: center; gap: 2.5rem;
  border-top: 1px solid var(--line); padding-top: 1.5rem;
}
.strip-place { color: var(--ink); padding-right: 2.5rem; border-right: 1px solid var(--line); white-space: nowrap; }
.strip-rule { height: 1px; background: var(--line-strong); }
@media (max-width: 64rem) {
  .strip-row { grid-template-columns: 1fr; gap: 1rem; }
  .strip-place { border-right: 0; padding-right: 0; }
  .strip-rule { display: none; }
}

/* ---------------- cases ----------------
   The three lines of work, each as one large card: the real thing on one side,
   what it is on the other. Sides alternate. */
.cases { padding: 6.5rem 0 3rem; }
.cases-head { max-width: 40rem; margin-bottom: 3.5rem; }
.cases-head .sub { margin-top: 1rem; }
.case-list { display: grid; gap: 5.5rem; }
.case {
  display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 3.5rem; align-items: center;
}
.case:nth-child(even) .case-media { order: 2; }
.case-media {
  position: relative; border-radius: var(--r-card); overflow: hidden;
  background: var(--bg-2); box-shadow: var(--shadow-card);
  transition: transform 300ms var(--ease-out);
}
.case-media:hover { transform: translateY(-3px); }
.case-media > img, .case-media > picture > img, .case-media video { width: 100%; height: auto; display: block; }
.case-media video { position: absolute; inset: 0; height: 100%; object-fit: cover; opacity: 0; transition: opacity 600ms ease; }
.case-media.is-playing video { opacity: 1; }
.case-media .corner { position: absolute; z-index: 2; color: var(--on-photo); text-shadow: var(--shadow-text); }
.case-media .corner .micro { color: inherit; }
.media-card.is-light .corner { color: var(--ink); text-shadow: none; }
.media-card.is-light .corner-bl .micro { background: rgba(255, 255, 255, .88); padding: .4rem .7rem; border-radius: var(--r-btn); line-height: 1.3; }
.case-media .corner-bl { bottom: 1.5rem; left: 1.5rem; }
/* the phone capture sits over the desktop capture's corner */
.media-card .inset {
  position: absolute; right: 1.5rem; bottom: 1.5rem; width: 24%; z-index: 2;
  border-radius: var(--r-inset); overflow: hidden; box-shadow: var(--shadow-inset);
  background: var(--card);
}
.media-card .inset img { position: static; width: 100%; height: auto; object-fit: initial; }
.case-copy h2, .case-copy h3 { max-width: 12ch; font-size: var(--fs-h2); line-height: 1; letter-spacing: -.035em; }
.case-status { display: inline-block; margin-top: 1rem; font-size: var(--fs-small); font-weight: 500; color: var(--blue-deep); background: var(--card); border: 1px solid var(--line); border-radius: var(--r-btn); padding: .35rem .8rem; }
.case-facts { margin-top: 1.5rem; }
.case-copy .sub { margin-top: 1.25rem; color: var(--ink); }
.case-copy p + p { margin-top: .9rem; }
.case-copy .case-note { color: var(--soft); }
.case-copy .btns { margin-top: 1.75rem; }
.case-tags { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.5rem; padding: 0; list-style: none; }
.case-tags li {
  font-size: var(--fs-small); font-weight: 500; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-btn); padding: .4rem .85rem;
}
@media (max-width: 64rem) {
  .cases { padding: 4.5rem 0 2rem; }
  .case-list { gap: 3.5rem; }
  .case { grid-template-columns: 1fr; gap: 1.75rem; }
  .case:nth-child(even) .case-media { order: 0; }
  .case-copy h2 { max-width: 16ch; }
}

/* ---------------- how a project runs ---------------- */
.process { padding: 4rem 0 5rem; }
.process-head { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 3rem; align-items: end; margin-bottom: 3rem; }
.process-head .sub { margin-top: 0; }
.steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2rem; list-style: none; margin: 0; padding: 0; }
.step { border-top: 2px solid var(--ink); padding-top: 1.25rem; }
.step h3 { display: flex; align-items: baseline; gap: .8rem; }
.step h3 .micro { color: var(--blue-deep); }
.step p { margin-top: .6rem; color: var(--soft); font-size: var(--fs-ui); }
@media (max-width: 64rem) {
  .process { padding: 3rem 0 3.5rem; }
  .process-head { grid-template-columns: 1fr; gap: 1rem; margin-bottom: 2rem; }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; }
}
@media (max-width: 30rem) { .steps { grid-template-columns: 1fr; } }

/* ---------------- contact ----------------
   One white card in the same radius as the photograph. */
.contact { padding: 2rem 0 6rem; }
.contact-card {
  background: var(--card); border-radius: var(--r-card); box-shadow: var(--shadow-soft);
  padding: 3.5rem; display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 3rem; align-items: center;
}
.contact-card h2 { max-width: 14ch; }
.contact-card .sub { margin-top: 1rem; }
.contact-card .btns { margin-top: 2rem; }
.facts { display: grid; gap: .9rem; margin: 0; }
.facts div { display: grid; grid-template-columns: 6.5rem 1fr; gap: 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.facts dt { color: var(--soft); font-size: var(--fs-small); font-weight: 500; padding-top: .1rem; }
.facts dd { margin: 0; overflow-wrap: anywhere; }
.facts a { color: var(--ink); text-decoration: none; }
.facts a:hover { color: var(--blue-deep); text-decoration: underline; }
.email-line { margin-top: 1.25rem; color: var(--soft); font-size: var(--fs-small); display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; overflow-wrap: anywhere; }
.email-address { color: var(--ink); }
.copy-btn {
  font: inherit; font-weight: 600; color: var(--blue-deep); background: none; border: 1px solid var(--line-strong);
  border-radius: var(--r-btn); padding: .25rem .7rem; cursor: pointer; min-height: 32px;
}
.copy-btn:hover { border-color: var(--blue-deep); }
@media (max-width: 64rem) {
  .contact { padding: 1rem 0 4rem; }
  .contact-card { grid-template-columns: 1fr; padding: 2rem 1.5rem; gap: 2rem; }
  .facts div { grid-template-columns: 1fr; gap: .15rem; }
}

/* ---------------- footer ---------------- */
.site-foot { padding: 3rem 0 3.5rem; border-top: 1px solid var(--line); position: relative; z-index: 1; }
.foot-grid { display: grid; grid-template-columns: 1.2fr 1fr 1fr 1.6fr; gap: 2.5rem; }
.foot-grid p { color: var(--soft); font-size: var(--fs-small); line-height: 1.55; max-width: none; }
.foot-grid strong { display: block; color: var(--ink); margin-bottom: .45rem; font-weight: 600; font-size: var(--fs-ui); }
.foot-grid a { color: var(--ink); text-decoration: none; }
.foot-grid a:hover { color: var(--blue-deep); text-decoration: underline; }
.colophon { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--line); display: flex; justify-content: space-between; gap: 2rem; flex-wrap: wrap; }
.colophon p { color: var(--soft); font-size: var(--fs-micro); letter-spacing: .04em; max-width: 60ch; }
@media (max-width: 64rem) { .foot-grid { grid-template-columns: 1fr 1fr; gap: 2rem; } }
@media (max-width: 40rem) { .foot-grid { grid-template-columns: 1fr; } }

/* ---------------- inner pages ----------------
   The same hero grammar at page scale: headline beside one rounded piece of
   media, then prose sections in one column. */
.page-hero { padding: 3rem 0 1rem; }
.page-hero .hero-grid { align-items: center; }
.page-hero h1 { max-width: 12ch; font-size: var(--fs-h2); line-height: .98; }
.page-hero .lead { margin-top: 1.25rem; }
.page-hero .btns { margin-top: 2rem; }
.section { padding: 4.5rem 0; }
.section-tint { background: var(--bg-2); }
.section-head { max-width: 46rem; margin: 0 0 2rem; }
.section-head .sub { margin-top: .75rem; }
.section-head.align-start { text-align: left; }
.section .wrap > p { color: var(--soft); }
.section .wrap > p + p { margin-top: 1rem; }
.section .wrap > p strong { color: var(--ink); }
.section .wrap > p + ul, .section .wrap > ul + p { margin-top: 1.25rem; }
.two-col { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 3rem; align-items: start; }
.two-col .section-head { margin-bottom: 0; }
@media (max-width: 64rem) {
  .page-hero { padding: 2rem 0 .5rem; }
  .page-hero h1 { max-width: 16ch; }
  .section { padding: 3.25rem 0; }
  .two-col { grid-template-columns: 1fr; gap: 1.5rem; }
}

.prose-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .9rem 2.5rem; color: var(--soft); }
@media (min-width: 64rem) {
  .prose-list { grid-template-columns: 1fr 1fr; }
  .prose-list li { max-width: 46ch; }
}
.prose-list li { position: relative; padding-left: 1.2rem; max-width: 62ch; }
.prose-list li::before { content: ''; position: absolute; left: 0; top: .66em; width: 6px; height: 6px; border-radius: 50%; background: var(--blue); }
.prose-list strong { color: var(--ink); }

/* the closing card on the inner pages, one anatomy with the home contact */
.cta-band {
  background: var(--card); border-radius: var(--r-card); box-shadow: var(--shadow-soft);
  padding: 3rem 3.5rem; display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 3rem; align-items: center;
}
.cta-band h2 { max-width: 14ch; }
.cta-band p { color: var(--soft); margin-top: 1rem; }
.cta-action { display: grid; gap: .8rem; justify-items: start; }
@media (max-width: 64rem) { .cta-band { grid-template-columns: 1fr; padding: 2rem 1.5rem; gap: 1.75rem; } }

/* ---------------- legal pages ---------------- */
.legal-main { padding-top: 2.5rem; }
.legal { max-width: 46rem; padding-bottom: 6rem; }
.legal h1 { font-size: var(--fs-h2); margin-bottom: 1rem; line-height: 1; }
.legal .lead { margin-bottom: .75rem; max-width: 60ch; }
.updated { color: var(--soft); font-size: var(--fs-small); }
.legal h2 { font-size: var(--fs-h3); margin: 3rem 0 .75rem; }
.legal ul { color: var(--soft); padding-left: 1.125rem; }
.legal li { margin-bottom: .5rem; }
.legal li::marker { color: var(--blue); }
.legal strong { color: var(--ink); }
.legal p { color: var(--soft); margin-bottom: 1rem; }
.legal p strong { color: var(--ink); }
.legal .btns { margin-top: 2rem; }

/* ---------------- motion ----------------
   Only what moves in the scene moves on the page: the water in the video, the
   palm shadows, light. One arrival on load, a 12px rise on a few panels as they
   enter. Double-gated on html.js and no-preference; everything is visible by
   default. */
@media (prefers-reduced-motion: no-preference) {
  /* Wind, not drift. Each shadow used to rotate about its own centre, which slides
     the whole shape uniformly and reads as the shadow sliding across the page. A
     frond pivots at the trunk: the base barely moves and the tips sweep. So palm-a
     and palm-c pivot at the corner they enter the frame from, where their trunk
     would be, and a 1.9deg rotation on a 900px shadow throws the tips about 30px.

     Two animations per shadow, on deliberately unrelated periods, because a single
     loop on `alternate` is a metronome and the eye locks onto it. `rotate` is the
     gust; `transform: skewX()` is the flex, which shears the top of the shadow
     against the bottom the way a frond bends. Their least common multiple is long
     enough that the pair never visibly repeats.

     The keyframes are uneven on purpose: a gust arrives, eases off, arrives harder.
     Evenly spaced stops read as a pendulum. */
  .js .palm-a {
    transform-origin: 0% 0%;
    animation: palm-gust 19s ease-in-out infinite, palm-shear 27s ease-in-out infinite;
  }
  .js .palm-c {
    transform-origin: 100% 0%;
    animation: palm-gust-c 23s ease-in-out infinite, palm-shear 31s ease-in-out infinite;
  }
  /* palm-b keeps the centre origin it has always had. Its base transform flips it
     (`scaleY(-1)`), and moving the origin would flip it about a different point and
     relocate the element. Its motion is carried entirely by the shear, which shears
     the same regardless of origin, so the chain is baked into its own keyframes. */
  .js .palm-b { animation: palm-shear-b 21s ease-in-out infinite; }
  .js .hero-copy > * { animation: rise 800ms var(--ease-out) both; }
  .js .hero-copy > :nth-child(2) { animation-delay: 120ms; }
  .js .hero-copy > :nth-child(3) { animation-delay: 220ms; }
  .js .hero-copy > :nth-child(4) { animation-delay: 300ms; }
  .js .hero-card { animation: settle 1000ms var(--ease-out) both; }
  .js .hero-card .corner { animation: fade-in 700ms ease 500ms both; }
  .js .strip-row { animation: fade-in 700ms ease 650ms both; }
  .js [data-reveal] { opacity: 0; transform: translateY(12px); transition: opacity 600ms ease, transform 700ms var(--ease-out); }
  .js [data-reveal].in { opacity: 1; transform: none; }
}
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes settle { from { opacity: 0; transform: scale(.985) translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes palm-gust {
  0%   { rotate: 0deg; }
  14%  { rotate: .9deg; }
  27%  { rotate: .3deg; }
  41%  { rotate: 1.9deg; }
  55%  { rotate: .8deg; }
  68%  { rotate: 1.4deg; }
  82%  { rotate: .5deg; }
  100% { rotate: 0deg; }
}
@keyframes palm-gust-c {
  0%   { rotate: 0deg; }
  16%  { rotate: -1.1deg; }
  31%  { rotate: -.4deg; }
  48%  { rotate: -1.7deg; }
  63%  { rotate: -.6deg; }
  79%  { rotate: -1.3deg; }
  100% { rotate: 0deg; }
}
@keyframes palm-shear {
  0%   { transform: skewX(0deg); }
  22%  { transform: skewX(1.1deg); }
  44%  { transform: skewX(.2deg); }
  67%  { transform: skewX(1.5deg); }
  100% { transform: skewX(0deg); }
}
@keyframes palm-shear-b {
  0%   { transform: scaleY(-1) rotate(6deg) skewX(0deg); }
  21%  { transform: scaleY(-1) rotate(6.9deg) skewX(1.2deg); }
  43%  { transform: scaleY(-1) rotate(6.2deg) skewX(.3deg); }
  66%  { transform: scaleY(-1) rotate(7.4deg) skewX(1.6deg); }
  100% { transform: scaleY(-1) rotate(6deg) skewX(0deg); }
}

/* ---------------- print ---------------- */
@media print {
  .palm, .site-head .nav-end, .nav-toggle, video { display: none !important; }
  body { background: #fff; }
}

/* a portrait piece of media takes the narrow column */
.hero-grid.is-tall { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
.case.is-tall { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
@media (max-width: 64rem) {
  .hero-grid.is-tall, .case.is-tall { grid-template-columns: 1fr; }
  .case.is-tall .case-media { max-width: 26rem; }
}

/* media variants */
.hero-card.is-portrait { aspect-ratio: 4 / 5; }
/* the poster carries its own marks in both bottom corners; the caption sits above them */
.hero-card.is-portrait .corner-bl { bottom: 4.5rem; }
.hero-card.is-shot > img { object-position: center top; }
.plate { margin: 0; }
.plate img { width: 100%; height: auto; display: block; }
.plate.is-mark { padding: 3rem 2rem; background: var(--card); display: grid; place-items: center; aspect-ratio: 16 / 10; }
.plate.is-mark img { width: 58%; max-width: 260px; }

/* ---------------- the OmniMarket poster's clouds ----------------
   Percentages of the 2160 by 2700 poster. The cloud sheets are masked to the
   band around the card so they never cross the headline or the stacks. */
.omni-clouds { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.omni-cloud {
  position: absolute; left: 52%; top: 12%; width: 62%; height: auto; opacity: .55;
  -webkit-mask-image: radial-gradient(ellipse 52% 50% at 50% 55%, var(--ink) 35%, transparent 72%);
  mask-image: radial-gradient(ellipse 52% 50% at 50% 55%, var(--ink) 35%, transparent 72%);
}
.omni-cloud-b { left: 44%; top: 17%; width: 70%; opacity: .38; }
.omni-card { position: absolute; left: 72.22%; top: 4.26%; width: 20.14%; height: auto; z-index: 2; }
@media (prefers-reduced-motion: no-preference) {
  .js .in-view .omni-cloud-a { animation: cloud-drift-a 46s ease-in-out infinite alternate; }
  .js .in-view .omni-cloud-b { animation: cloud-drift-b 58s ease-in-out infinite alternate; }
}
@keyframes cloud-drift-a { from { transform: translate(-5%, 0); } to { transform: translate(5%, 1.5%); } }
@keyframes cloud-drift-b { from { transform: translate(4%, 1%); } to { transform: translate(-5%, -1%); } }

\n

/* ----- the automation renders' motion (src/components/work/AutoFlow.astro) -----
   The tube highlight is a SHORT pulse of the royal blue's lit state (--blue-lit),
   about a quarter of the tube's own thickness in length, swept left to right inside a
   layer masked by the render's own tube pixels. Short and blue on purpose: a long lit
   stretch turns the tube two-tone, and the unlit half then reads as though it were in
   shadow, which is the defect this shape avoids. It can never leave the tube and it vanishes where
   the tube passes behind an icon. Two earlier versions failed here and are worth not
   repeating: traced SVG centerlines drifted off the drawn curves; a WHITE band over
   a saturated blue tube washed it pale, which made the unlit tube either side read as
   though it were in shadow; `backdrop-filter` is not clipped by an ancestor's mask in
   Chrome, so it brightened the whole card; and revealing a brightened second copy of
   the render trips the image-reuse gate, correctly. The mask and the image are both cover-cropped in the same box, so
   they stay aligned at any size. Then one accent per variant: the check button glows,
   the flagged ring ripples, the phone's dot blinks. Clouds drift in the far corners.
   All of it runs only while the card is on screen: /script.js sets `in-view`. */
.auto-flow { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.auto-flow svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.auto-tubes {
  position: absolute; inset: 0; overflow: hidden;
  -webkit-mask-image: var(--tube-mask); mask-image: var(--tube-mask);
  -webkit-mask-size: cover; mask-size: cover;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.auto-sweep {
  position: absolute; top: 0; bottom: 0; left: 0; width: 3.5%;
  transform: translateX(-150%); opacity: .9;
  background: linear-gradient(90deg, transparent 0%, var(--blue-lit) 50%, transparent 100%);
}
.auto-flow .ripple { fill: none; stroke: var(--blue); stroke-width: 10; opacity: 0; transform-box: fill-box; transform-origin: center; }
.auto-flow .blink { fill: var(--blue); opacity: 0; transform-box: fill-box; transform-origin: center; }
.auto-flow .glow { fill: #FFFFFF; opacity: 0; transform-box: fill-box; transform-origin: center; }
.auto-cloud {
  position: absolute; width: 34%; height: auto; opacity: .6;
  -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, var(--ink) 40%, transparent 72%);
  mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, var(--ink) 40%, transparent 72%);
}
.auto-cloud-a { right: -8%; top: -14%; }
.auto-cloud-b { left: -10%; bottom: -16%; width: 40%; opacity: .5; }
@media (prefers-reduced-motion: no-preference) {
  .js .in-view .auto-sweep { animation: tube-sweep 8s linear infinite; }
  .js .in-view .ripple { animation: ripple 8s ease-out infinite; }
  .js .in-view .glow { animation: glow 8s ease-in-out infinite; }
  .js .in-view .blink { animation: blink 8s ease-out infinite; }
  .js .in-view .auto-cloud-a { animation: cloud-drift-a 52s ease-in-out infinite alternate; }
  .js .in-view .auto-cloud-b { animation: cloud-drift-b 64s ease-in-out infinite alternate; }
}
@keyframes tube-sweep {
  0% { transform: translateX(-150%); }
  68%, 100% { transform: translateX(2950%); }
}
@keyframes ripple {
  0% { opacity: .9; transform: scale(1); }
  22%, 100% { opacity: 0; transform: scale(2.6); }
}
@keyframes glow {
  0%, 52% { opacity: 0; }
  60% { opacity: .45; }
  74%, 100% { opacity: 0; }
}
@keyframes blink {
  0%, 56% { opacity: 0; transform: scale(1); }
  60% { opacity: .9; transform: scale(1); }
  80%, 100% { opacity: 0; transform: scale(3); }
}
