/* animations.css — scroll reveal, hero entrance, and motion-safety overrides.
   Progressive enhancement: effects only apply once .js-animations is present
   on <html> (see the inline bootstrap script in index.html's <head>).
   Cascade contract: the reduced-motion and print overrides live at the
   bottom of this file and use !important — any rule appended above them
   must not rely on winning specificity against those two blocks. */

:root {
  --anim-duration: 550ms;
  /* 図版だけ長め。見出しや本文は「読み始める前に整う」速さでよいが、図は
     それ自体を眺める対象なので、ゆっくり現れるほうが落ち着いて見える。
     図とキャプションの両方がこの値を使うので、増減はここ1か所でよい。 */
  --anim-duration-figure: 900ms;
  --anim-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --anim-distance: 18px;
}

/* page loader */
.page-loader {
  transition: opacity 200ms var(--anim-ease);
}
.page-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}
.page-loader-spinner {
  animation: page-loader-spin 700ms linear infinite;
}
@keyframes page-loader-spin {
  to { transform: rotate(360deg); }
}

html.js-animations [data-animate] {
  opacity: 0;
  transform: translateY(var(--anim-distance));
  transition: opacity var(--anim-duration) var(--anim-ease),
              transform var(--anim-duration) var(--anim-ease);
}
html.js-animations [data-animate].in-view {
  opacity: 1;
  transform: translateY(0);
}

/* 図版はフェードのみ。見出しや本文は 18px 迫り上がってくるが、画面幅いっぱいの
   図が同じ距離を動くと、画像そのものがずれたように見えて落ち着かない。位置は
   動かさず不透明度だけで出す。
   figcaption は図に連動させる。個別に [data-animate] を付けると、背の低い
   キャプションが監視の閾値（15%）に届くのが図よりかなり遅れ、図が出てから間を
   置いて出てくる。 */
html.js-animations .figure[data-animate] {
  transform: none;
  transition: opacity var(--anim-duration-figure) var(--anim-ease);
}
html.js-animations .figure[data-animate] + .figcaption {
  opacity: 0;
  transition: opacity var(--anim-duration-figure) var(--anim-ease);
}
html.js-animations .figure[data-animate].in-view + .figcaption {
  opacity: 1;
}

/* hero entrance stagger — the five direct children of .hero-inner reveal in order */
.hero-inner > [data-animate]:nth-child(1) { transition-delay: 0ms; }
.hero-inner > [data-animate]:nth-child(2) { transition-delay: 90ms; }
.hero-inner > [data-animate]:nth-child(3) { transition-delay: 180ms; }
.hero-inner > [data-animate]:nth-child(4) { transition-delay: 270ms; }
.hero-inner > [data-animate]:nth-child(5) { transition-delay: 360ms; }

/* mobile nav toggle — hamburger bars morph into an X */
.nav-toggle-bar {
  transition: transform 200ms var(--anim-ease), opacity 200ms var(--anim-ease);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* hover micro-interactions */
.btn {
  transition: transform 150ms var(--anim-ease), background 150ms var(--anim-ease);
}
.btn:hover,
.btn:focus-visible {
  transform: translateY(-1px);
}

.blueprint > .corner {
  transition: transform 200ms var(--anim-ease), color 200ms var(--anim-ease);
}

/* リンクセルは hover で地を変えられない（ロゴの使用規定。styles.css 参照）ので、
   枠線の色変化がその代わりになる。 */
.link-cell {
  transition: border-color 200ms var(--anim-ease);
}
.card:hover > .corner.tl,
.group-card:hover > .corner.tl,
.link-cell:hover > .corner.tl,
.link-cell:focus-visible > .corner.tl { transform: translate(-2px, -2px); }
.card:hover > .corner.tr,
.group-card:hover > .corner.tr,
.link-cell:hover > .corner.tr,
.link-cell:focus-visible > .corner.tr { transform: translate(2px, -2px); }
.card:hover > .corner.bl,
.group-card:hover > .corner.bl,
.link-cell:hover > .corner.bl,
.link-cell:focus-visible > .corner.bl { transform: translate(-2px, 2px); }
.card:hover > .corner.br,
.group-card:hover > .corner.br,
.link-cell:hover > .corner.br,
.link-cell:focus-visible > .corner.br { transform: translate(2px, 2px); }

/* figure outline glow — disabled for now, kept here to re-enable later
.figure:hover > .corner.tl,
.figure:focus-within > .corner.tl { transform: translate(-2px, -2px); }
.figure:hover > .corner.tr,
.figure:focus-within > .corner.tr { transform: translate(2px, -2px); }
.figure:hover > .corner.bl,
.figure:focus-within > .corner.bl { transform: translate(-2px, 2px); }
.figure:hover > .corner.br,
.figure:focus-within > .corner.br { transform: translate(2px, 2px); }
.figure.blueprint {
  transition: border-color 200ms var(--anim-ease), box-shadow 200ms var(--anim-ease);
}
.figure.blueprint:hover,
.figure.blueprint:focus-within {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-accent), 0 0 18px color-mix(in srgb, var(--color-accent) 55%, transparent);
}
.figure.blueprint:hover > .corner,
.figure.blueprint:focus-within > .corner {
  color: var(--color-accent);
}
*/

/* no-JS fallback: back-to-top has no gated transition to inherit, so
   give it one directly for the hover lift below. */
.back-to-top {
  transition: transform 150ms var(--anim-ease), background 150ms var(--anim-ease);
}
.back-to-top:hover,
.back-to-top:focus-visible {
  transform: translateY(-2px);
}

/* back-to-top — under the reveal engine, hidden near the top and shown
   once the hero has scrolled out of view (toggled by animations.js). */
html.js-animations .back-to-top {
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity 200ms var(--anim-ease), transform 200ms var(--anim-ease), background 150ms var(--anim-ease);
}
html.js-animations .back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
html.js-animations .back-to-top.is-visible:hover,
html.js-animations .back-to-top.is-visible:focus-visible {
  transform: translateY(-2px);
}

/* tech detail accordion — the 0fr→1fr grid row set up in styles.css animates
   to the content's own height, so no max-height guess is needed. 420ms rather
   than the 550ms reveal duration: this is a direct response to a click, and
   the panel travels 700–1200px, which reads as sluggish over half a second.

   visibility rides along on the same clock. It is not interpolable, so it
   flips at whichever end keeps the text on screen: opening, it turns visible
   immediately; closing, it stays visible until the collapse has finished
   rather than blanking the text at the first pixel. One declaration covers
   both directions.

   Under prefers-reduced-motion the block below drops both transitions and the
   panel simply appears — the layout in styles.css stands on its own. */
.tech-detail {
  transition: grid-template-rows 420ms var(--anim-ease);
}
.tech-detail > .tech-detail-body {
  transition: visibility 420ms var(--anim-ease);
}

/* the toggle's crosshair — the vertical bar sweeps onto the horizontal one,
   leaving a "－". Rotation is about the bar's own centre, which sits on the
   horizontal bar, so the two coincide at 90deg.
   Half the panel's 420ms: the mark is an 11px object and the panel is a
   700–1200px one, so matching their durations made the mark look like it was
   dragging. Settling first also reads correctly — the button confirms the
   press, then the text arrives. */
.toggle-mark::before {
  transition: transform 210ms var(--anim-ease);
}

/* motion-safety overrides — kept last so they reliably win the cascade
   against every rule above, regardless of what gets appended later. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
  /* figcaption は自身が [data-animate] ではなく、隣の図に連動して隠れている
     ので、ここでも名指しで戻す必要がある。 */
  [data-animate],
  .figure[data-animate] + .figcaption {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* print — [data-animate] defaults to opacity:0 pending scroll-reveal, which
   would otherwise leave everything below the fold blank on a printed page
   or PDF export. Force full visibility here too. */
@media print {
  [data-animate],
  .figure[data-animate] + .figcaption {
    opacity: 1 !important;
    transform: none !important;
  }
  .back-to-top {
    display: none;
  }
}
