/*
 * Copyright Stefan Press Consulting
 * Created using Claude Code.
 */
/*
 * hero.css -- the showroom band: backdrop, car, wordmark and contact line.
 *
 * Shared by index.html (full screen, and the way in) and app.html (compact,
 * for a page you have already reached). Extracted from index.html when the
 * dashboard started drawing the same car, so the two cannot drift.
 */
body{overflow-x:hidden}

  .hero{
    /* 100vh on iOS is the LARGE viewport — the height the page would have if
       Safari's toolbars were hidden. They are not hidden on first load, so the
       showroom is taller than the screen and the car sits partly below the
       fold, on the one page whose entire job is showing the car. dvh is the
       viewport as it actually is right now; the vh line stays underneath it as
       the fallback for anything that does not know dvh. */
    min-height:100vh;
    min-height:100dvh;
    display:flex;flex-direction:column;
    background:
      radial-gradient(1100px 620px at 50% 34%, #223247 0%, rgba(34,50,71,0) 70%),
      linear-gradient(180deg,#0b0f14 0%,#10161f 55%,#080b10 100%);
    position:relative;overflow:hidden;
  }
  .hero::after{ /* floor sheen */
    content:'';position:absolute;left:0;right:0;bottom:0;height:34%;
    background:linear-gradient(180deg,rgba(255,255,255,.028),transparent 65%);
    pointer-events:none;
  }
  .hero-head{text-align:center;padding:44px 20px 8px;position:relative;z-index:3}
  /* THE BIGGEST THING ON THE PAGE FOLLOWS THE COMPANY'S ACCENT.
     It was three hard-coded ambers — #f0c05a, #a97c1c and the glow behind
     them — so a firm that had chosen navy got a navy top bar, navy buttons,
     navy figures and then this, in amber, at three times the size of anything
     else on the screen. The tokens are the ones style.css already derives from
     brand_accent; unskinned, they are the same three colours as before. */
  .hero-head h1{
    font-size:clamp(1.9rem,5.2vw,3.1rem);margin:0 0 10px;letter-spacing:.5px;
    background:linear-gradient(180deg,#fff 10%,var(--brand2) 58%,var(--brand-edge) 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    text-shadow:0 2px 30px rgba(var(--brand-rgb),.14);font-weight:700;
  }
  .hero-head p{color:#93a3b8;margin:0;font-size:clamp(.9rem,2.2vw,1.05rem);letter-spacing:.3px}
  .hero-head .since{color:#5f6f84;font-size:.8rem;margin-top:9px;letter-spacing:2.4px;text-transform:uppercase}

  .stage{flex:1;display:flex;align-items:center;justify-content:center;padding:8px 16px 30px;position:relative;z-index:2}
  .car{width:min(1120px,96vw);height:auto;display:block;filter:drop-shadow(0 40px 46px rgba(0,0,0,.6))}

  /* The way in.
     Deliberately unannounced — no label, no pulse, no permanent glow. The lamp
     is dark until the pointer is actually on it, so finding it is the reward.
     Keyboard and screen-reader users still reach it: it is a real link with an
     aria-label and a visible focus ring. */
  #headlightHit{cursor:pointer}
  #headlightHit:focus{outline:none}
  .lampGlow{opacity:0;transition:opacity .45s ease;pointer-events:none}
  .lampCore{transition:fill .35s ease}
  #headlightHit:hover .lampGlow,
  #headlightHit:focus-visible .lampGlow{opacity:1}
  #headlightHit:focus-visible .lampRim{stroke:#fff;stroke-width:3}
  .beam{opacity:0;transition:opacity .7s ease;pointer-events:none}
  #headlightHit:hover .beam,.beam.on{opacity:.8}

  .tag-tail{color:var(--brand);font-weight:600;white-space:nowrap}
  .tag-tail em{font-style:italic;color:var(--brand2);letter-spacing:.5px}

  .foot{
    text-align:center;padding:0 20px 30px;color:#4f5d70;font-size:.78rem;
    position:relative;z-index:3;line-height:1.7;
  }
  .foot a{color:#7b8ca3}

/* Compact variant -- the dashboard.
   The same showroom, no longer the whole screen: it sits above the tiles
   instead of replacing them, so the page still opens on the work. */
.hero.hero-compact{min-height:0;border-radius:14px;overflow:hidden;margin-bottom:22px}
.hero.hero-compact .hero-head{padding:26px 20px 0}
.hero.hero-compact .hero-head h1{font-size:clamp(1.4rem,3.4vw,2.1rem)}
.hero.hero-compact .stage{padding:0 16px 6px}
.hero.hero-compact .car{width:min(760px,88%);filter:drop-shadow(0 22px 26px rgba(0,0,0,.5))}
.hero.hero-compact .foot{padding:0 20px 18px}

/* Inert lamp on the dashboard: no pointer, no hover glow, nothing to click. */
.lamp-inert{pointer-events:none}

/* ---------- Unread bulletins: flash the lights ----------
   The dashboard car is decoration until the board has something unread, and
   then it becomes the notification — headlight and tail lamp together, front
   and back, so it reads as the car signalling rather than as one lamp glitching.

   AAS_CAR.alert() adds .alerting for a four-pulse burst and takes it off again
   when the burst is done, leaving the car dark. The burst catches the eye; what
   persists is the "N new posts" notice on the board itself, which can be read
   and acted on. A light that blinks for ever is a light people stop seeing.

   No lingering half-lit state on purpose. An earlier version left a low glow
   behind and it would not reliably go out again once the animation had run —
   the lamp stayed lit after the board was marked read, which says the opposite
   of the truth. One class, two states, nothing to get stuck in.

   The tail glow is dark at rest and only ever lit by this, so it is defined
   here alongside the animation rather than next to the headlight rules. */
.tailGlow{opacity:0;pointer-events:none}

/*
 * FLASH-FLASH, THEN FOUR SECONDS OF DARK.
 *
 * Two short pulses inside the first second of a five-second cycle, and nothing
 * for the rest of it. A light that pulses continuously is one people learn to
 * ignore, which is the opposite of what a notification is for; a pair with a
 * long gap reads as a car signalling. The dark is 80% of the cycle so it never
 * competes with the work on the page.
 *
 * `infinite` HERE rather than a burst repeated by a timer. The old shape had
 * the burst length in car.js and the gap in board.js, two files that had to
 * agree about one rhythm and expressed it in different units. A cycle that
 * carries its own gap cannot drift from itself.
 */
@keyframes carLampFlash{
  0%   {opacity:0}
  3%   {opacity:1}
  7%   {opacity:0}
  11%  {opacity:1}
  15%  {opacity:0}
  100% {opacity:0}
}
/* THE HEADLIGHT ONLY. The tail lamp is a BUTTON — it glows on hover and on
   focus to say so — and a lamp that also blinks by itself is one light saying
   two things. The front of the car has nothing else to say. */
.car.alerting .lampGlow{animation:carLampFlash 5s ease-in-out infinite}
/* The bulb brightens with the glow rather than staying lit through the dark,
   which would leave the car permanently half-on between flashes. */
@keyframes carLampCore{
  0%,100%{fill:#fff8e6}
  3%,7%  {fill:#fffdf4}
  11%,15%{fill:#fffdf4}
}
.car.alerting .lampCore{animation:carLampCore 5s ease-in-out infinite}

/* Flashing lights are exactly what this preference is for. The lamps still come
   up, and still say the same thing — they simply hold steady instead of
   blinking, and go out with the class like everything else. */
@media (prefers-reduced-motion:reduce){
  /* Flashing lights are exactly what this preference is for. The lamp still
     comes up and still says the same thing — it simply holds steady instead of
     blinking, and goes out with the class like everything else. */
  .car.alerting .lampGlow{animation:none;opacity:.55}
  .car.alerting .lampCore{animation:none;fill:#fffdf4}
}

/* ---------------------------------------------------------------------------
   TURNING ROUND. Press the tail lamp: the car goes AWOOOOOGA, swings a quarter
   turn to show you its back, reads its own name off the number plate, and then
   finishes the turn. Press it again and it comes back the same way.

   THE ROTATION IS A TRANSITION AND THE HOP IS AN ANIMATION, running at the
   same time on two DIFFERENT properties. Doing both in `transform` means the
   keyframes must restate the rotation at every step, and then the return trip
   needs a second set of keyframes with every angle reversed. `rotate` and
   `translate` are their own properties now, so each half is written once and
   the way back is the same declaration with a different angle.

   --turn and --rear are set by car.js, and the DURATIONS ARE WRITTEN TWICE —
   once here as transitions, once there as the timers that sequence them. They
   have to agree. Nothing reads one off the other, so a change made in one
   place and not the other shows up as a swap you can watch happening.
   --------------------------------------------------------------------------- */
/*
 * THE ROTATION IS ON THE TWO GROUPS, NOT ON THE SVG.
 *
 * It was on the SVG, which was fine while the only two stops were 0 and 180.
 * A stop at 90 needs the back of the car to appear exactly where the profile
 * vanishes — and a child cannot do that from inside a rotated parent, because
 * a 3D transform on an element with no `transform-style: preserve-3d` FLATTENS
 * its children into the parent's plane. A parent turned 90 degrees is a plane
 * with no width, and nothing nested inside it can rotate back out.
 *
 * So `.side` and `.rear` are siblings sharing one pivot, each with its own
 * angle. The profile runs 0 → 90 → 180 and is edge-on in the middle; the back
 * runs -90 → 0 → 90 and is edge-on at both ends. Their crossover is at exactly
 * the moment neither has any width, which is why the swap cannot be seen.
 *
 * `.car` keeps the hop, and only the hop. It is the one movement that belongs
 * to the whole car rather than to a face of it.
 */
.car{ transform-origin:50% 62%; }

/*
 * ONE PIVOT MEANS ONE NUMBER, NOT `50%` TWICE.
 *
 * `fill-box` makes 50% the centre of each group's OWN bounding box, so the
 * sentence above — two siblings, one pivot — was false the moment it was
 * written. Measured in the browser:
 *
 *   .side  bounding box  -60 .. 1158   50% = 549
 *   .rear  bounding box  414 ..  786   50% = 600
 *
 * and the -60 is the HEADLIGHT BEAM, a polygon that reaches out past the nose
 * and is transparent until the lamp is hovered. An invisible decoration was
 * setting where the car turned. Mirroring the profile about 549 instead of
 * about its own centre threw it 138 units to the left — 11% of the frame,
 * about 90px on an iPad — so the car facing right sat visibly off-centre while
 * the car facing left did not.
 *
 * `view-box` puts both groups in the same coordinate system: the viewBox, the
 * one thing on the page that cannot move. 608 is where the paintwork actually
 * balances, measured with the beam, the two glows and the smoke taken out —
 * the profile therefore lands in exactly the same place at 180 degrees as it
 * starts in at 0.
 *
 * The Y half does nothing for a `rotate:y` — a turn about a vertical axis does
 * not care how far up that axis the origin sits — and is left as a percentage
 * so it goes on agreeing with the hop above.
 */
.side{
  transform-box:view-box;
  transform-origin:608px 62%;
  rotate:y var(--turn, 0deg);
  transition:rotate 620ms cubic-bezier(.4,.05,.35,1);   /* must match SWING_MS */
}
.rear{
  transform-box:view-box;
  transform-origin:608px 62%;
  rotate:y var(--rear, 90deg);
  transition:rotate 260ms cubic-bezier(.3,.6,.4,1);     /* must match OPEN_MS  */
  opacity:0;
}
/* Hidden outright rather than left edge-on: a zero-width group still rounds up
   to a hairline down the middle of the car on some displays, and two hairlines
   crossing at the swap is exactly the artefact this is trying to avoid. */
.car.rear-on .rear{opacity:1}
.car.rear-on .side{opacity:0}

/* The hop: crouch, leap, land a little heavy. A car that pivots on the spot
   like a turntable is correct and dull; this one puts its weight into it. */
@keyframes carTurnHop{
  0%   {translate:0 0;      scale:1 1}
  14%  {translate:0 10px;   scale:1.03 .93}    /* crouch */
  42%  {translate:0 -34px;  scale:.98 1.04}    /* airborne */
  68%  {translate:0 -20px;  scale:1 1}
  86%  {translate:0 6px;    scale:1.04 .94}    /* landing squash */
  100% {translate:0 0;      scale:1 1}
}
.car.turning{animation:carTurnHop 1.1s cubic-bezier(.3,.7,.3,1)}

/* Both lamps burn through the manoeuvre. Not the four-pulse alert burst —
   that one MEANS something (unread posts) and borrowing it here would teach
   people to ignore it. This is faster, and it stops when the car stops. */
@keyframes carTurnFlash{
  0%,100%{opacity:.15}
  50%    {opacity:1}
}
.car.turning .lampGlow,
.car.turning .tailGlow{animation:carTurnFlash .26s ease-in-out 6}
.car.turning .lampCore{fill:#fffdf4}
.car.turning .tailCore{opacity:1}

/* The plate revolves: squashed to nothing and back, which is how a prism
   turning is drawn in two dimensions. car.js swaps the word at the pinch,
   where the plate has no height and the change cannot be seen happening. */
@keyframes plateFlip{
  0%   {scale:1 1}
  50%  {scale:1 .04}
  100% {scale:1 1}
}
/* .42s here, FLIP_MS there. car.js swaps the word at half of this, where the
   plate has no height and the change cannot be seen happening. */
.plate.flipping{animation:plateFlip .42s ease-in-out}
/*
 * THE PLATE NEEDS NO COUNTER-ROTATION ANY MORE, and that is the fix.
 *
 * It used to live on the profile, where the car's own rotateY(180deg) mirrored
 * everything inside it, text included — so after one press the plate read
 * LASIARPPA, a rendering artefact wearing the costume of a joke. It was
 * corrected by giving the plate the same rotation as the car, nesting one
 * inside the other so 180 and 180 made 360.
 *
 * That worked, and it was answering the wrong question: a number plate is not
 * visible from the side of a car. It is on the back now, in the group that is
 * only ever shown square-on, so there is no mirroring left to undo. The bug is
 * gone rather than cancelled — and the writing is where somebody stands to
 * read it, which is what the DB5's revolving plate was always about.
 */
.plate{
  transform-box:fill-box;
  transform-origin:50% 50%;
}

/* The tail lamp is a control, so it says so on hover and on focus — the same
   affordance the headlight has, and the same reason. */
#taillightHit{cursor:pointer}
#taillightHit:hover .tailGlow,
#taillightHit:focus-visible .tailGlow{opacity:.85}
#taillightHit:focus-visible{outline:2px solid var(--brand,#f0c05a);outline-offset:6px;border-radius:8px}

/* Reduced motion: it still turns, because facing the other way is the POINT
   and is not decoration. It simply arrives there without the leap, the squash
   or the strobe. */
@media (prefers-reduced-motion:reduce){
  /* It still turns, still stops at the back, and still reads the name out —
     all three are the POINT and none of them is decoration. What goes is the
     leap, the squash and the strobe, and the swings get short enough that the
     movement reads as a change of state rather than as motion. */
  .side{transition:rotate .12s linear}
  .rear{transition:rotate .12s linear}
  .car.turning{animation:none}
  /* The plate still changes — it is information, not decoration — it simply
     does not spin to do it. */
  .plate.flipping{animation:none}
  .car.turning .lampGlow,
  .car.turning .tailGlow{animation:none;opacity:.55}
}

/* The headlight is the only control on the sign-in page, and on a phone it is
   too small to hit reliably.
   The SVG is a shrinkable flex item in a .stage with 16px of side padding, so
   the scale is (viewport - 32) / 1200. The transparent hit circle is r="54",
   which is 108 user units across — 29.5 CSS px on a 360px Android, 32.2 on a
   390px iPhone. Apple's minimum is 44.
   r is a real SVG 2 geometry property in CSS (Safari 9+, Chrome 43+, Firefox
   69+), and where it is not supported the r="54" attribute simply stands. So
   the LAMP stays exactly the size it was drawn; only the target grows.
   Scoped to a media query on purpose: at the 1120px desktop car this would be
   a 168px invisible pointer region across the whole nose, which gives the
   puzzle away. Above 560px the untouched target already clears 47px. */
@media (max-width:560px){
  #headlightHit > circle:first-of-type{ r:90px; }
}

/* No pointer means no approach, so nothing on a touch screen can hint at the
   lamp before it is pressed. The dark-until-found puzzle is deliberate and
   stays; this only confirms the press once it lands, so a near-miss is
   distinguishable from a hit. */
@media (hover:none){
  #headlightHit:active .lampGlow{opacity:1}
}

@media(max-width:600px){
  .hero-head{padding:28px 16px 4px}
  .hero.hero-compact .hero-head{padding:20px 14px 0}
}

/* ---------------------------------------------------------------------------
   THE DOOR HANDLE, which opens the dashboard.

   It looks like a handle and not like a button, deliberately: the joke only
   works if you find it. What it does have is the affordances a control must
   have whether or not it is announced — a pointer, a hover, and a focus ring
   for anyone arriving by keyboard, who gets the aria-label the SVG carries.
   --------------------------------------------------------------------------- */
#doorHandleHit{cursor:pointer}
.doorHandle{transition:filter .18s ease}
#doorHandleHit:hover .doorHandle{filter:brightness(1.5)}
#doorHandleHit:focus{outline:none}
#doorHandleHit:focus-visible .doorHandle{filter:brightness(1.8)}
#doorHandleHit:focus-visible{outline:2px solid var(--brand,#f0c05a);outline-offset:4px;border-radius:6px}

/* ---------------------------------------------------------------------------
   SMOKE, one turn in four — see puff() in car.js for why it is not every time.

   Three puffs per pipe at different sizes and delays, so it reads as billowing
   rather than as one circle growing. They start inside the tip and drift up and
   back: `back` is +x here because the exhausts point at the viewer in the rear
   view, and smoke from a pipe pointing at you spreads outward as much as it
   rises.

   opacity is animated rather than display, so the clearing is a fade over
   seconds — car.js takes the class off a while after the car has stopped, and
   an instant cut would look like the animation ending rather than like exhaust
   drifting off.
   --------------------------------------------------------------------------- */
.smoke .puff{
  fill:#c9d3dd;
  opacity:0;
  transform-box:fill-box;
  transform-origin:50% 50%;
  pointer-events:none;
}
@keyframes puffOut{
  0%   {opacity:0;   scale:.30; translate:0 0}
  15%  {opacity:.62}
  55%  {opacity:.34}
  100% {opacity:0;   scale:3.4;  translate:0 -62px}
}
/* Denser and slower than it was: bigger at the end, drifting further, and
   five overlapping lifetimes rather than three so there is always more than
   one in the air. The opacity is up a little as well — this is a tired old
   engine at the one moment it is pointed at the viewer. */
.car.smoking .puff{animation:puffOut 2.8s ease-out infinite}
.car.smoking .p2{animation-delay:.45s;animation-duration:3.2s}
.car.smoking .p3{animation-delay:.9s; animation-duration:2.5s}
.car.smoking .p4{animation-delay:1.35s;animation-duration:3.4s}
.car.smoking .p5{animation-delay:1.8s;animation-duration:2.7s}
/* Leaving the class removes the animation, and a puff mid-flight would vanish
   on the frame it went. The fade-out lives here: the group dims over a second
   and a half whichever frame the animation happened to be on. */
.smoke{transition:opacity 1.5s ease}
.car:not(.smoking) .smoke{opacity:0}

@media (prefers-reduced-motion:reduce){
  /* The smoke is a joke, and a joke is exactly what this preference is for.
     No drifting: it appears, holds, and goes with the class. */
  .car.smoking .puff{animation:none;opacity:.34;scale:1.6}
}
