/* AEB — global stylesheet.
   =========================================================================
   THIS FILE HOLDS ONLY WHAT INLINE STYLES CANNOT EXPRESS.

   The app styles everything with inline `style={{}}` reading the tokens, and
   that is not changing -- this is not the start of a migration. What belongs
   here is the set of things an inline style attribute has no syntax for:

     :focus-visible   :hover on an element that has no JS hover handler
     media queries    prefers-reduced-motion   ::selection   pseudo-elements

   Anything an inline style CAN express stays inline. If you are tempted to
   add a component's colours or padding here, that is the wrong file.

   ONE CASCADE FACT GOVERNS EVERYTHING BELOW. An inline style attribute beats
   any rule in this file (short of !important). So a rule here only lands on a
   property the component does not already set inline. That is why the hover
   work below is transitions rather than colours: nearly every interactive
   element in this app sets `background` inline and drives its own hover state
   in JS, so a `:hover { background: ... }` rule here would be silently inert,
   while a `transition` rule makes those existing JS state changes animate.

   Rules are wrapped in :where() to keep their specificity at zero, so a
   component that needs to override one can do so with a plain class or an
   inline style rather than an escalating selector war.

   Note this file has DIVERGED from design-system/styles.css, which is still
   the import-only entry point for the design system itself. Per the UI polish
   plan's port-vs-source guardrail, the app-level rules live in this served
   copy; syncing them back is a separate job.
   ========================================================================= */

@import url('./fonts.css');
@import url('./colors.css');
@import url('./typography.css');
@import url('./spacing.css');

:root {
  /* Mirrors the sticky nav in website/src/components/NavBar.jsx (height: 62,
     position: sticky, top: 0). Not a design token -- a measurement this
     stylesheet needs so a scrolled-to or newly focused element does not land
     underneath the nav bar. If the nav height changes, change it here too. */
  --nav-height: 62px;
  --scroll-offset: calc(var(--nav-height) + var(--space-4));
}

/* -------------------------------------------------------------------------
   Base
   Moved out of the inline <style> block in website/index.html so there is one
   place these live and no cascade race between the two.
   ------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-body);
  /* An element with no explicit fontSize should inherit the system's stated
     body size, not the browser's coincidental 16px default. They agree today;
     they are only guaranteed to agree because this line exists. */
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings balance; running text gets the looser, orphan-avoiding pass.
   Deliberately no font-family, size, weight or margin here -- those are the
   component's business and are set inline. */
:where(h1, h2, h3, h4, h5, h6) {
  text-wrap: balance;
}

:where(p, li, blockquote, dd) {
  text-wrap: pretty;
}

/* The nav is sticky, so anything scrolled to needs to clear it. */
:where(h1, h2, h3, h4, h5, h6, [id]) {
  scroll-margin-top: var(--scroll-offset);
}

/* -------------------------------------------------------------------------
   Links
   ------------------------------------------------------------------------- */

:where(a) {
  color: var(--text-link);
  text-underline-offset: 2px;
}

:where(a):hover {
  color: var(--text-link-hover);
}

/* -------------------------------------------------------------------------
   Focus

   :focus-visible, never :focus -- a ring on every mouse click is noise, and
   the point of this block is that a KEYBOARD user can see where they are.
   Before this landed there were six bare `outline: 'none'` declarations in
   the app with no replacement, which meant keyboard navigation was invisible
   on the profile editor, the mentors page, the employer profile and the
   workflow builder. Those are now deleted; this is what replaces them.

   No border-radius is set here on purpose: an outline already follows the
   element's own border-radius, and setting one would reshape elements that
   do not declare it inline.

   Input.jsx and Select.jsx set their own inline outline on focus and are
   correct as they stand -- inline wins, so this rule leaves them alone.
   ------------------------------------------------------------------------- */

:where(
  a[href],
  button,
  input,
  select,
  textarea,
  summary,
  [role="button"],
  [role="tab"],
  [role="menuitem"],
  [role="switch"],
  [contenteditable="true"],
  [tabindex]:not([tabindex="-1"])
):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Checkbox, Radio and Switch hide the real control at 0x0 and paint a sibling <span> as the
   box the user actually sees. The rule above would therefore put the ring on a zero-sized
   element -- i.e. nowhere -- and every checkbox, radio and switch in the app was invisible to
   keyboard focus. Put the ring on the painted sibling instead. This depends on the input and
   its box being ADJACENT siblings; the three components carry a comment saying so. */
:where(input[type="checkbox"], input[type="radio"]):focus-visible + span {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* A focused element must not end up underneath the sticky nav or scrolled
   out of a drawer. Pairs with the heading rule above. */
:where(
  a[href],
  button,
  input,
  select,
  textarea,
  summary,
  [role="button"],
  [tabindex]:not([tabindex="-1"])
) {
  scroll-margin-top: var(--scroll-offset);
  scroll-margin-bottom: var(--space-4);
}

/* -------------------------------------------------------------------------
   Motion

   Explicit property lists only -- never `transition: all`, which animates
   layout properties nobody asked to animate and costs a frame doing it.

   Components that declare their own inline transition (Button, Card) keep
   theirs; inline wins. This rule is what gives the nine files that drive
   hover through onMouseEnter/useState a transition they never had, so those
   state changes fade instead of snapping.
   ------------------------------------------------------------------------- */

:where(a, button, summary, [role="button"], [role="tab"]) {
  transition:
    color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

:where(input, select, textarea) {
  transition:
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

/* The logo owns the far-left Home position. On narrow screens the brand and
   account controls stay visible while the route row scrolls on its own line. */
:where(.aeb-site-header) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--space-16);
  padding: 0 var(--space-6);
}

:where(.aeb-site-primary-nav),
:where(.aeb-site-account) {
  display: flex;
  align-items: center;
}

:where(.aeb-site-brand) {
  display: flex;
  flex-shrink: 0;
  margin-right: var(--space-4);
  text-decoration: none;
}

:where(.aeb-site-primary-nav) {
  gap: var(--space-1);
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scrollbar-width: thin;
}

:where(.aeb-site-account) {
  gap: var(--space-5);
  flex-shrink: 0;
  margin-left: var(--space-4);
}

:where(.aeb-job-board-hero) {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}

:where(.aeb-job-board-filters) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  align-items: end;
}

/* Job Board filter rail: filters on the left, results on the right. Stacks below ~820px. */
:where(.aeb-job-board-layout) {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

:where(.aeb-job-board-form-grid) {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

:where(.aeb-crew-request-grid) {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

@media (max-width: 640px) {
  :where(.aeb-site-header) {
    flex-wrap: wrap;
    padding: 0 var(--space-4);
  }

  :where(.aeb-site-account) {
    order: 2;
    width: auto;
    margin-left: auto;
    padding: var(--space-2) 0;
  }

  :where(.aeb-site-brand) {
    order: 1;
    margin-right: var(--space-3);
  }

  :where(.aeb-site-primary-nav) {
    order: 3;
    flex-basis: 100%;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scrollbar-width: thin;
  }

  :where(.aeb-site-primary-nav a) {
    white-space: nowrap;
  }

  :where(.aeb-job-board-hero) {
    align-items: stretch;
    flex-direction: column;
  }

  :where(.aeb-job-board-filters),
  :where(.aeb-job-board-form-grid),
  :where(.aeb-crew-request-grid) {
    grid-template-columns: 1fr;
  }
}

/* Every transition above, and every inline one in the app, has a reduced
   variant -- which is what !important is doing here. It is the one place in
   this stylesheet allowed to outrank an inline style. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
    /* The landing page staggers its reveals with an inline transition-delay. Without this the
       text still appears instantly but arrives in sequence over half a second, which is the
       animation the visitor asked not to see. */
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* -------------------------------------------------------------------------
   Scroll reveal and the landing hero

   Driven from website/src/components/Reveal.jsx, which toggles `.is-visible`
   from an IntersectionObserver. The reason these two states are HERE rather
   than inline is the reduced-motion block above: it can turn the movement off
   while leaving the state change intact, so a visitor who has asked for less
   motion still reads every word -- it simply does not travel.

   Both selectors sit in :where() and so both have zero specificity; source
   order is what makes `.is-visible` win. Keep them in this order.
   ------------------------------------------------------------------------- */

:where(.aeb-reveal) {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}

:where(.aeb-reveal.is-visible) {
  opacity: 1;
  transform: none;
}

/* The hero's backdrop drifts across its own frame so a still photograph is not
   simply parked behind the masthead. Slow and small on purpose -- 8% over 24
   seconds reads as depth, not as a moving image. Applied to the ACTIVE slide
   only, so the offscreen ones are not animating for nobody. */
@keyframes aeb-drift {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}

:where(.aeb-hero-slide.is-active) {
  animation: aeb-drift 24s var(--ease-standard) forwards;
}

/* The scroll cue at the foot of the hero. A single hint, three times, then it
   stops -- an indefinite loop is the attention-seeking motion section 1 of
   docs/DESIGN.md rules out. */
@keyframes aeb-nudge {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50%      { transform: translateY(6px); opacity: 1; }
}

:where(.aeb-scroll-cue) {
  animation: aeb-nudge 2.4s var(--ease-standard) 3;
}

/* -------------------------------------------------------------------------
   The StillWorking crew scene

   Drives the inline SVG in website/src/components/feedback/StillWorking.jsx.
   Keyframes cannot be written as an inline style object, which is the whole
   reason these are here rather than on the elements themselves.

   THE MOTION IS DELIBERATELY AMBIENT, NOT A CUE. docs/DESIGN.md section 1
   rules out attention-seeking motion, and the scroll cue below obeys that by
   running three times and stopping. These loop, and the distinction that
   makes it correct is that the crew scene IS the page -- there is no content
   for it to compete with, and it is asking for nothing. Amplitudes stay small
   and the periods are long and mutually prime, so the figures never fall into
   step and nothing travels far enough to pull the eye off the heading.

   REDUCED MOTION IS HANDLED BY THE GLOBAL BLOCK ABOVE, and every declaration
   here is written so that it lands on a composed still rather than a pose.
   That block collapses animation-duration to 0.01ms with iteration-count 1,
   and none of these sets animation-fill-mode, so each element settles back on
   its own BASE style the instant the run ends. The base styles below are
   therefore the still frame: no transform, and an explicit opacity on the
   beam.

   THE BEAM'S BASE OPACITY IS THE SUBTLE ONE. `aeb-sw-flicker` animates
   opacity, so without a base value a reduced-motion visitor would get the
   element's default opacity of 1 and a beam at double strength. Setting it in
   the rule, and matching it at the flicker's own 0%/100%, means the still
   frame and the resting frame of the animation are the same picture.

   transform-box: fill-box throughout, so every transform-origin below reads
   against that element's own bounding box: the beam's apex at the lamp, the
   gaffer's shoulder, the boom operator's grip, the tripod head, the
   director's knee and shoulder.
   ------------------------------------------------------------------------- */

:where(
  .aeb-sw-beam,
  .aeb-sw-lamp,
  .aeb-sw-gaffer-arm,
  .aeb-sw-boom,
  .aeb-sw-rig,
  .aeb-sw-shin,
  .aeb-sw-frame-arm,
  .aeb-sw-bob
) {
  transform-box: fill-box;
}

@keyframes aeb-sw-sweep {
  0%, 100% { transform: rotate(-3.5deg); }
  50%      { transform: rotate(4deg); }
}

/* A lamp settling on a gel, not a fault. Two frames out of a hundred. */
@keyframes aeb-sw-flicker {
  0%, 91%   { opacity: 0.5; }
  92%, 94%  { opacity: 0.18; }
  95%, 100% { opacity: 0.5; }
}

@keyframes aeb-sw-tweak {
  0%, 100% { transform: rotate(0deg); }
  50%      { transform: rotate(-16deg); }
}

@keyframes aeb-sw-swing {
  0%, 100% { transform: rotate(-2.5deg); }
  50%      { transform: rotate(3deg); }
}

@keyframes aeb-sw-pan {
  0%, 100% { transform: rotate(-4deg); }
  50%      { transform: rotate(5deg); }
}

@keyframes aeb-sw-bounce {
  0%, 100% { transform: rotate(0deg); }
  50%      { transform: rotate(-11deg); }
}

/* The director frames up with a hand, holds it, and drops it again. */
@keyframes aeb-sw-callit {
  0%, 62%   { transform: rotate(0deg); }
  70%, 86%  { transform: rotate(-52deg); }
  94%, 100% { transform: rotate(0deg); }
}

@keyframes aeb-sw-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-2.5px); }
}

:where(.aeb-sw-beam) {
  transform-origin: 0 0;
  opacity: 0.5;
  animation:
    aeb-sw-sweep 9s var(--ease-standard) infinite,
    aeb-sw-flicker 3.1s steps(1, end) infinite;
}

/* Same keyframes and the same period as the beam, so the lamp and the light it
   throws stay locked together instead of drifting apart. */
:where(.aeb-sw-lamp) {
  transform-origin: 4px 16px;
  animation: aeb-sw-sweep 9s var(--ease-standard) infinite;
}

:where(.aeb-sw-gaffer-arm) {
  transform-origin: 2px 2px;
  animation: aeb-sw-tweak 3.8s var(--ease-standard) infinite;
}

:where(.aeb-sw-boom) {
  transform-origin: 4px 44px;
  animation: aeb-sw-swing 10.6s var(--ease-standard) infinite;
}

:where(.aeb-sw-rig) {
  transform-origin: 46px 52px;
  animation: aeb-sw-pan 22s var(--ease-standard) infinite;
}

:where(.aeb-sw-shin) {
  transform-origin: 3px 3px;
  animation: aeb-sw-bounce 2.1s var(--ease-standard) infinite;
}

:where(.aeb-sw-frame-arm) {
  transform-origin: 3px 3px;
  animation: aeb-sw-callit 7.5s var(--ease-standard) infinite;
}

:where(.aeb-sw-bob) {
  animation: aeb-sw-bob 8.4s var(--ease-standard) infinite;
}

/* -------------------------------------------------------------------------
   Selection
   ------------------------------------------------------------------------- */

::selection {
  background: var(--gold-100);
  color: var(--navy-900);
}

/* -------------------------------------------------------------------------
   Utilities

   A short, closed list. Each one exists because inline styles cannot express
   it well or because it must be identical everywhere it appears.
   ------------------------------------------------------------------------- */

/* Figures that line up in a column: day-rate ranges, analytics counts, the
   Billing tables, every chart axis. Proportional digits make a column of
   numbers ragged and make a changing number jitter. */
.tabular-nums {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.text-balance {
  text-wrap: balance;
}

.text-pretty {
  text-wrap: pretty;
}

/* Available to any element, not just the ones the base rules cover. */
.scroll-offset {
  scroll-margin-top: var(--scroll-offset);
}

/* The Employer Directory borrows the screenshot's filter-rail + listing structure.
   These columns live here because they collapse at two responsive breakpoints; all
   visual styling remains inline with the rest of the app. */
:where(.aeb-directory-layout) {
  display: grid;
  grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

:where(.aeb-directory-filters) {
  position: sticky;
  top: calc(var(--nav-height) + var(--space-4));
}

:where(.aeb-directory-result) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(160px, 200px);
  gap: var(--space-5);
  align-items: start;
}

@media (max-width: 900px) {
  :where(.aeb-directory-layout) {
    grid-template-columns: 1fr;
  }

  :where(.aeb-directory-filters) {
    position: static;
  }
}

@media (max-width: 640px) {
  :where(.aeb-directory-result) {
    grid-template-columns: auto minmax(0, 1fr);
  }

  :where(.aeb-directory-result > aside) {
    grid-column: 1 / -1;
  }
}

/* Text for screen readers only. Not currently used; here so that the next
   icon-only control has a correct answer available to it. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Landing feature bands: the text link under each band nudges its arrow. A transition only, so
   the reduced-motion block still flattens it. */
:where(.aeb-feature-link) span {
  display: inline-block;
  transition: transform var(--duration-base) var(--ease-standard);
}
:where(.aeb-feature-link:hover) span {
  transform: translateX(4px);
}
