/* The site's motion. Four packs; a page is in exactly one of them, named
   on <html data-motion>. portal/motion.ts is the vocabulary and says why
   there are four.

   THE HARD RULE THIS FILE IS BUILT AROUND: nothing here is allowed to
   hide content that a visitor might never see revealed. So the hidden
   state of a reveal is not written by this stylesheet against every
   candidate element — it applies only once /motion.js has added the
   attribute, and /motion.js adds it only when it has already checked
   that it can and will animate it back. A visitor with JavaScript off,
   an IntersectionObserver-less browser, or `prefers-reduced-motion:
   reduce` never meets the hidden state at all. The reduce block at the
   foot of this file is the second line of that defence, not the first.

   No colour is invented here. Everything tinted reads a token from
   styles.css, so a published theme carries its motion with it. */

:root {
  /* One easing across the estate: the curve oppwa.com.au reveals on. */
  --motion-ease: cubic-bezier(.2, .7, .3, 1);
  --motion-rise: 22px;
  /* Doubled from .7s on Hugh's read of it live, 2026-09-21: at .7s the
     rise was over before the eye arrived at it. The stagger is NOT
     doubled with it — at 70ms a four-across still reads as a wave, and
     doubling it too would put the fourth card most of a second behind
     the first on top of a longer fade. */
  --motion-dur: 1.4s;
  --motion-step: 70ms;   /* the wave across a four-across grid */
}

/* =====================================================================
   The reveal: fade and rise as a section comes up the screen.
   Used by house, glide and curtain. /motion.js writes data-reveal on
   the elements it will observe, and data-reveal="in" when they arrive.
   ===================================================================== */
/* THE REVEAL OWNS `transition` ON ANYTHING IT REVEALS, AND THAT IS WHY
   THE HOVER BELOW USES `translate` RATHER THAN `transform`.

   Two `transition` shorthands on one element do not merge — the more
   specific one replaces the other outright. The pack hover rules are
   `html[data-motion="house"] .card`, which outranks `[data-reveal]`, so
   until 2026-09-21 every card, step and directory card revealed in the
   HOVER's .3s instead of the reveal's duration, with its stagger delay
   reset to zero. Measured in Chrome: transition-duration 0.3s on a
   .step that should have read 1.4s. It is why the reveal was described
   as too short — the number in --motion-dur was not the number running.

   The fix is not specificity games. The hover lifts with `translate`,
   an independent property that composes with `transform` instead of
   replacing it, so the two never want the same declaration; this rule
   carries both timings; and the pack rules below set only the lifted
   state. A pack that adds a hover must lift with translate/scale too. */
[data-reveal] {
  opacity: 0;
  transform: translateY(var(--motion-rise));
  transition:
    opacity var(--motion-dur) var(--motion-ease),
    transform var(--motion-dur) var(--motion-ease),
    translate .3s var(--motion-ease),
    scale .3s var(--motion-ease),
    box-shadow .3s var(--motion-ease);
  transition-delay: var(--motion-delay, 0ms), var(--motion-delay, 0ms), 0s, 0s, 0s;
}
[data-reveal="in"] { opacity: 1; transform: none; }

/* The stagger, by position among its own siblings rather than among
   every revealed element on the page — which is what makes a row of
   four arrive as a wave and a lone heading arrive at once. Four steps
   and then it repeats, so a long directory never delays a card by a
   second and a half. */
[data-reveal]:nth-child(4n + 2) { --motion-delay: var(--motion-step); }
[data-reveal]:nth-child(4n + 3) { --motion-delay: calc(var(--motion-step) * 2); }
[data-reveal]:nth-child(4n + 4) { --motion-delay: calc(var(--motion-step) * 3); }

/* =====================================================================
   HOUSE — what oppwa.com.au already does, brought across unchanged in
   its feel: the reveal above at its full length, and the ambient drift
   behind a deep panel. Page to page is the browser's own instant change
   (portal/motion.ts: pageTransitions false, so no /page-transitions.css).
   ===================================================================== */
:where(html[data-motion="house"]) :where(.card, .step, .dir-card) {
  transition: translate .3s var(--motion-ease), box-shadow .3s var(--motion-ease);
}
html[data-motion="house"] .card:hover,
html[data-motion="house"] .step:hover,
html[data-motion="house"] .dir-card:hover {
  translate: 0 -3px;
}

/* The drift: two slow rings behind the deep bands, the same figure the
   OPPWA hero carries. Decoration in the strictest sense — a pseudo
   element on a section that already exists, so there is nothing extra
   in the markup and nothing for a screen reader to meet. */
html[data-motion="house"] .band-deep,
html[data-motion="house"] .cta-deep { position: relative; overflow: hidden; }
html[data-motion="house"] .band-deep > *,
html[data-motion="house"] .cta-deep > * { position: relative; z-index: 1; }
html[data-motion="house"] .band-deep::after,
html[data-motion="house"] .cta-deep::after {
  content: '';
  position: absolute;
  bottom: -180px; right: -120px;
  width: 460px; height: 460px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .18);
  pointer-events: none;
  animation: mo-drift 11s ease-in-out infinite;
}
html[data-motion="house"] .band-deep::before {
  content: '';
  position: absolute;
  top: -120px; left: -100px;
  width: 380px; height: 380px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .14);
  pointer-events: none;
  animation: mo-drift 9s ease-in-out infinite;
}
@keyframes mo-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* =====================================================================
   GLIDE — the pack about moving BETWEEN pages. Its page transitions are
   in /page-transitions.css; in-page it is the same reveal, shortened,
   because a page that has just glided in should not then spend another
   three quarters of a second assembling itself.
   ===================================================================== */
html[data-motion="glide"] {
  --motion-rise: 14px;
  --motion-dur: 1s;
  --motion-step: 55ms;
}
:where(html[data-motion="glide"]) :where(.card, .step, .dir-card, .logo-tile) {
  transition: translate .3s var(--motion-ease), scale .3s var(--motion-ease), box-shadow .3s var(--motion-ease);
}
html[data-motion="glide"] .card:hover,
html[data-motion="glide"] .step:hover,
html[data-motion="glide"] .dir-card:hover {
  translate: 0 -3px;
  box-shadow: 0 2px 4px rgba(15, 23, 12, .05), 0 14px 30px rgba(15, 23, 12, .09);
}
html[data-motion="glide"] .logo-tile:hover { translate: 0 -2px; scale: 1.03; }

/* The nav's underline sweeps out from the middle rather than appearing. */
html[data-motion="glide"] .site-nav a:not(.btn-nav):not(.nav-signin) { position: relative; }
html[data-motion="glide"] .site-nav a:not(.btn-nav):not(.nav-signin)::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -6px;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transition: transform .25s var(--motion-ease);
}
html[data-motion="glide"] .site-nav a:hover::after,
html[data-motion="glide"] .site-nav a[aria-current="page"]::after { transform: scaleX(1); }

/* =====================================================================
   CURTAIN — theatrical. Its between-pages wipe is in
   /page-transitions.css. In the page, headings rise from behind their
   own baseline: the mask is a clipped wrapper /motion.js adds around
   the heading's own text, so the text itself is untouched and a
   selection or a find-in-page still lands on it.
   ===================================================================== */
html[data-motion="curtain"] { --motion-dur: 1.5s; --motion-rise: 26px; }

[data-reveal-mask] {
  display: block;
  overflow: hidden;
  /* The clip would take the descenders off a heading with a g or a p in
     it. The padding gives them room and the negative margin gives the
     room back, so nothing below the heading moves. */
  padding-bottom: .12em;
  margin-bottom: -.12em;
}
[data-reveal-mask] > .mo-line {
  display: block;
  transform: translateY(100%);
  transition: transform 1.5s var(--motion-ease);
  transition-delay: var(--motion-delay, 0ms);
}
[data-reveal-mask="in"] > .mo-line { transform: none; }

/* The bands wipe in from the left edge rather than fading.

   THE WIPE IS A MASK AND NOT A clip-path, AND THAT IS NOT A STYLE
   PREFERENCE. `clip-path: inset(0 100% 0 0)` was written here first and
   it made the pack ship blank: an IntersectionObserver measures the
   target's clipped visual rectangle, a rectangle clipped to no width
   intersects nothing, the threshold is never met, and the element it
   was hiding is never told to come back. Measured in Chrome over the
   built site — 24 elements marked, 24 still hidden after scrolling past
   all of them, against 10 revealed with the clip removed. A mask is a
   paint effect: it takes nothing off the geometry the observer reads,
   and it wipes the same way. Verified the same way, on the same pages.
   Nothing that hides an element may narrow what the observer sees. */
html[data-motion="curtain"] [data-reveal] {
  transform: none;
  opacity: 1;
  -webkit-mask-image: linear-gradient(to right, #000 50%, transparent 50%);
  mask-image: linear-gradient(to right, #000 50%, transparent 50%);
  -webkit-mask-size: 200% 100%;
  mask-size: 200% 100%;
  -webkit-mask-position: 100% 0;
  mask-position: 100% 0;
  transition:
    mask-position 1.5s var(--motion-ease),
    -webkit-mask-position 1.5s var(--motion-ease);
  transition-delay: var(--motion-delay, 0ms);
}
html[data-motion="curtain"] [data-reveal="in"] {
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
}

/* A rule of the cooperative's accent under every band heading, drawn
   left to right as the heading arrives. */
html[data-motion="curtain"] .band-head { position: relative; }
html[data-motion="curtain"] .band-head::after {
  content: '';
  position: absolute;
  left: 0; bottom: -18px;
  height: 3px; width: 64px;
  background: var(--red);
  transform-origin: left;
  transition: transform 1s var(--motion-ease) .3s;
}
/* Collapsed only once /motion.js has said it is running: a rule that is
   drawn by a reveal must not start at zero width on a page where the
   reveal will never happen. */
html[data-motion="curtain"][data-motion-ready] .band-head::after { transform: scaleX(0); }
html[data-motion="curtain"] .band-head.center::after { left: 50%; margin-left: -32px; transform-origin: center; }
/* Drawn when the heading inside it has finished rising. /motion.js sets
   the attribute; keying it off the heading with :has() would leave the
   rule collapsed forever in a browser without :has, and a decoration
   that can never appear is the failure this file is written against. */
html[data-motion="curtain"] .band-head[data-rule="in"]::after { transform: scaleX(1); }

/* =====================================================================
   QUIET — nothing moves on its own. No reveal: the page is simply there.
   What motion there is, is answering something — the pointer, or your
   having arrived at a number.
   ===================================================================== */

/* The header condenses once you are past the top of the page, and
   follows you down. This is the pack's one structural opinion: the
   other three leave the header where the document puts it. */
html[data-motion="quiet"] .site-header {
  position: sticky;
  top: 0;
  z-index: 25;
  background: var(--bg);
  transition: padding .25s var(--motion-ease), box-shadow .25s var(--motion-ease);
}
html[data-motion="quiet"][data-scrolled] .site-header {
  padding-top: calc(env(safe-area-inset-top, 0px) + 10px);
  padding-bottom: 10px;
  box-shadow: 0 1px 0 var(--border), 0 6px 18px rgba(15, 23, 12, .06);
}
html[data-motion="quiet"][data-scrolled] .brand-logo { height: 32px; transition: height .25s var(--motion-ease); }

/* Quiet has no reveal, so nothing here is being overridden today. It is
   written the same way regardless: the rule above is an invariant for
   every pack, and a pack that keeps `transform` for its hover is a trap
   set for whoever gives this pack a reveal later. */
:where(html[data-motion="quiet"]) :where(.card, .step, .dir-card, .logo-tile, .fee-card) {
  transition: translate .25s var(--motion-ease), scale .25s var(--motion-ease),
              box-shadow .25s var(--motion-ease), border-color .25s var(--motion-ease);
}
html[data-motion="quiet"] .card:hover,
html[data-motion="quiet"] .step:hover,
html[data-motion="quiet"] .dir-card:hover,
html[data-motion="quiet"] .fee-card:hover {
  translate: 0 -4px;
  box-shadow: 0 2px 4px rgba(15, 23, 12, .05), 0 16px 34px rgba(15, 23, 12, .10);
}
html[data-motion="quiet"] .logo-tile:hover { scale: 1.05; }
:where(html[data-motion="quiet"]) :where(.btn) {
  transition: translate .12s var(--motion-ease), scale .12s var(--motion-ease), background-color .2s var(--motion-ease);
}
html[data-motion="quiet"] .btn:active { translate: 0 1px; scale: .985; }

/* The counted number holds its final width while it runs, so the band
   does not reflow four times a second on its way up. /motion.js sets
   the character count it measured from the finished value. */
[data-count] { font-variant-numeric: tabular-nums; }

/* =====================================================================
   Filtering the directory. Cards that come back after a facet changes
   fade in rather than appearing; cards that were already showing are
   left alone. /motion.js marks the ones that changed.
   ===================================================================== */
.dir-card[data-filtered-in] { animation: mo-fade-in .28s var(--motion-ease) both; }
@keyframes mo-fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* =====================================================================
   The visitor who has asked for less motion gets none of it. Stated
   here as well as in /motion.js because the two failures are different:
   the script's check stops elements being hidden in the first place,
   and this stops anything that is purely a stylesheet's doing — the
   drift, the hovers, the wipe — from running at all.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal], [data-reveal="in"] {
    opacity: 1 !important;
    transform: none !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
    transition: none !important;
  }
  [data-reveal-mask] > .mo-line { transform: none !important; transition: none !important; }
  html[data-motion] .band-deep::before,
  html[data-motion] .band-deep::after,
  html[data-motion] .cta-deep::after,
  html[data-motion] .band-head::after,
  html[data-motion] .site-nav a::after {
    animation: none !important;
    transition: none !important;
    transform: none !important;
  }
  .dir-card[data-filtered-in] { animation: none !important; }
  html[data-motion] .card:hover,
  html[data-motion] .step:hover,
  html[data-motion] .dir-card:hover,
  html[data-motion] .fee-card:hover,
  html[data-motion] .logo-tile:hover,
  html[data-motion] .btn:active {
    transform: none !important;
    translate: none !important;
    scale: none !important;
  }
}

/* =====================================================================
   The pack switcher. Present only when a page has been opened with
   ?motion= on the URL (see /motion.js), which no published link carries
   and no visitor types: a built page is in one pack and shows none of
   this. It is here so the four can be compared on the real site, under
   the real headers, through portal/serve.ts — the only local server the
   built site is looked at through.
   ===================================================================== */
.mo-switch {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border-radius: 999px;
  background: rgba(24, 30, 22, .93);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .28);
  font-family: var(--font);
  view-transition-name: mo-switch;
}
.mo-switch-label {
  color: #A6AF9F;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  padding: 0 8px 0 10px;
}
.mo-switch button {
  border: 0;
  cursor: pointer;
  border-radius: 999px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: #E8EDE5;
  background: rgba(255, 255, 255, .08);
}
.mo-switch button:hover { background: rgba(255, 255, 255, .18); }
.mo-switch button[aria-pressed="true"] { background: #fff; color: #1F2A18; }
.mo-switch-close { font-size: 15px !important; padding: 8px 12px !important; }
@media (max-width: 700px) {
  .mo-switch { flex-wrap: wrap; justify-content: center; max-width: calc(100% - 24px); }
}
