/* ==========================================================================
   fx/kinetic.css – live and kinetic details (styles for fx/kinetic.js)
   1. Countdown row in the deadline tile (static text without the script;
      with it, changed digits roll through a clip box)
   2. Magnetic buttons ([data-magnetic]): press feedback; the pull itself is
      written by the script as the individual `translate` property
   3. Pointer light on the green fields ([data-spot]), ::before only
   4. Row sweeps on index rows, CSS only, ::before only (::after on the rows
      belongs to fx/scroll.css)
   5. The menu sheet's view transition: a wipe with the IWI cut as its edge
   Nothing here hides content. Every movement (translate, scale, clip-path wipe, roll) is limited
   to prefers-reduced-motion: no-preference. Under reduce a gentle tier keeps light and opacity:
   the pointer light still follows a fine pointer and fades, row sweeps fade in at full length,
   the menu sheet cross-fades (0.2s); the countdown changes its text without rolling and the
   magnetic pull and press scale are off.
   White text on HSG green stays white (5.09:1) at every moment.
   ========================================================================== */

/* Mirrors the spot position into the light without restyling the field's content. */
@property --mx {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 50%;
}
@property --my {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 50%;
}

/* ── 1. Countdown ──────────────────────────────────────────────────────── */
/* A ruled row of three figures (days, hours, minutes; no seconds, nothing changes every second)
   between the time line and the button. Three equal columns across the tile's full width. */
.countdown {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 16px;
  margin-top: 28px;
  padding-top: 14px;
  border-top: 1px solid #fff;
  color: #fff;
  font-variant-numeric: tabular-nums lining-nums;
}
.countdown__part { display: flex; align-items: baseline; gap: 5px; min-width: 0; white-space: nowrap; }
.countdown__n {
  font-size: clamp(28px, 2.2vw, 32px); font-weight: 300; line-height: 1.15; letter-spacing: -0.01em;
}
.countdown__u { font-size: 14px; line-height: 1; letter-spacing: 0.02em; }
/* The script hides the row once the deadline has passed; `display: grid` above would override
   the UA's [hidden] rule. */
.countdown[hidden] { display: none; }

/* With the script: each figure is one clip box (vertical edges exact, a little air at the sides),
   each digit a cell; a changed digit leaves upwards while the new one rises from below. */
html.js .countdown__n { clip-path: inset(0 -0.25em); }
.countdown__dg { position: relative; display: inline-block; }
.countdown__glyph { display: block; }
.countdown__glyph.is-out { position: absolute; top: 0; left: 0; }
@media all {
  .countdown__glyph.is-in { animation: kinetic-roll-in 0.3s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) both; }
  .countdown__glyph.is-out { animation: kinetic-roll-out 0.3s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) both; }
}
@media not all { /* was: reduced motion (ignored by decision, see DESIGN.md) */
  .countdown__glyph.is-out { display: none; }
}
@keyframes kinetic-roll-in { from { transform: translateY(100%); } to { transform: none; } }
@keyframes kinetic-roll-out { from { transform: none; } to { transform: translateY(-100%); } }

@media (max-width: 719px) {
  .countdown { margin-top: 12px; padding-top: 8px; column-gap: 8px; }
  .countdown__n { font-size: 24px; }
  .countdown__u { font-size: 13px; }
}
/* A printed countdown is stale the moment it leaves the printer. */
@media print {
  .countdown { display: none; }
}

/* ── 2. Magnetic buttons ───────────────────────────────────────────────── */
/* The pull is the `translate` property (set by the script), so it composes with the arrow's
   existing hover transform. Here only a short press; site.css transitions are extended. */
@media (hover: hover) and (pointer: fine) {
  html.js .btn[data-magnetic] {
    transition: background-color 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease),
      scale 0.35s var(--ease);
  }
  html.js .btn[data-magnetic]:active:not([disabled]):not([aria-busy="true"]) {
    scale: 0.97;
    transition: background-color 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease),
      scale 0.1s var(--ease);
  }
}

/* ── 3. Pointer light on the green fields ──────────────────────────────── */
/* A soft white light under the content, following the pointer (--mx/--my, set by the script).
   The alpha is capped per field so white text stays >= 4.5:1 and mint >= 5:1 under the brightest
   point: 0.05 on HSG green, 0.07 on dark green. Do not raise these.
   Both motion tiers: the light is light, not movement (nothing travels on its own; it sits
   wherever the pointer is and only its opacity fades), so it stays on under reduced motion. */
.tile, .section--submit { --spot-a: 0.05; }
.section--deep, .footer { --spot-a: 0.07; }

@media (hover: hover) and (pointer: fine) {
  /* Zero specificity: any layout rule of another module wins over these two declarations. */
  :where(html.js [data-spot]) { position: relative; isolation: isolate; }
  html.js [data-spot]::before {
    --mx: inherit;
    --my: inherit;
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1; /* above the field's own fill, below all of its content (the field is isolated) */
    background: radial-gradient(280px circle at var(--mx) var(--my),
      rgba(255, 255, 255, var(--spot-a, 0.05)), rgba(255, 255, 255, 0) 70%);
    opacity: 0;
    transition: opacity 0.5s var(--ease);
    pointer-events: none;
  }
  /* While lit, the light gets its own layer, so moving it repaints the light only, not the type. */
  html.js [data-spot].is-lit::before { opacity: 1; will-change: opacity; }
  /* A closed (grey) tile gets no light: grey has no contrast to spare. */
  html.js .tile--closed[data-spot]::before { display: none; }
}

/* ── 4. Row sweeps ─────────────────────────────────────────────────────── */
/* A 2px line sweeps along the row's bottom edge from the left, the row's first text element
   moves 6px to the right. HSG green on white and band rows, mint on the dark green faculty.
   The line is collapsed with `transform` (not the individual `scale` property), so a browser
   without `scale` never shows it as a permanent bar under every row. */
@media (hover: hover) {
  .dates > li, .agenda > li, .person, .index > li, .index > div { position: relative; }
  .dates > li::before, .agenda > li::before, .person::before, .index > li::before, .index > div::before {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -1px; /* covers the 1px hairline and one pixel above it */
    z-index: 1; /* above the hairline that fx/scroll.css draws with ::after */
    height: 2px;
    background: var(--green);
    transform: scaleX(0);
    transform-origin: 0 50%;
    pointer-events: none;
  }
  .person::before { background: var(--mint); }

  .dates > li:not(.is-key):hover > .dates__date { color: var(--green); }
  .agenda > li:hover > .agenda__time, .index > div:hover > .index__key { color: var(--ink); }
}
@media (hover: hover) {
  .dates > li::before, .agenda > li::before, .person::before, .index > li::before, .index > div::before {
    transition: transform 0.45s var(--ease);
  }
  .dates > li:hover::before, .agenda > li:hover::before, .person:hover::before,
  .index > li:hover::before, .index > div:hover::before { transform: none; }

  /* Zero specificity, so a transition another module sets on these elements is not replaced. */
  :where(.dates > li, .person, .index > div) > :where(:first-child) {
    transition: translate 0.45s var(--ease), color 0.3s var(--ease);
  }
  /* fx/scroll.css gives the agenda times a colour transition (0,4,2); this extends that list
     (same specificity, loaded later) instead of replacing it. */
  html.js .agenda > li > .agenda__time,
  html.js.fx-scroll .agenda > li > .agenda__time {
    transition: translate 0.45s var(--ease), color 0.3s var(--fx-soft, cubic-bezier(0.33, 1, 0.68, 1));
  }
  .agenda > li > .agenda__time { transition: translate 0.45s var(--ease), color 0.3s var(--ease); }

  .dates > li:hover > :first-child, .agenda > li:hover > :first-child,
  .person:hover > :first-child, .index > div:hover > :first-child { translate: 6px 0; }
}
/* Reduced motion: the line fades in at full length, nothing moves. */
@media not all { /* was: reduced motion (ignored by decision, see DESIGN.md) */
  .dates > li::before, .agenda > li::before, .person::before, .index > li::before, .index > div::before {
    transform: none; opacity: 0; transition: opacity 0.2s linear;
  }
  .dates > li:hover::before, .agenda > li:hover::before, .person:hover::before,
  .index > li:hover::before, .index > div:hover::before { opacity: 1; }
  :where(.dates > li, .agenda > li, .person, .index > div) > :where(:first-child) { transition: color 0.2s linear; }
}
@media (forced-colors: active) {
  .dates > li::before, .agenda > li::before, .person::before, .index > li::before, .index > div::before,
  html.js [data-spot]::before { display: none; }
}

/* ── 5. Menu sheet: view transition with the IWI cut ───────────────────── */
/* The script adds .has-vt when it will run the menu in a view transition (supported; both motion
   tiers), and .is-menu-vt while one runs. Only the sheet is captured: the rest of the page stays
   live and clickable. The sheet's own keyframe in site.css (no-preference only) steps aside so it
   never plays twice. No-preference: the IWI wipe. Reduce: a 0.2s cross-fade, opacity only. */
html.is-menu-vt { view-transition-name: none; }
html.is-menu-vt .menu__sheet { view-transition-name: menu-sheet; }
html.is-menu-vt::view-transition { pointer-events: none; }
@media all {
  html.has-vt .menu[open] .menu__sheet { animation: none; }
  html.is-menu-vt::view-transition-new(menu-sheet) {
    animation: kinetic-sheet-in 0.45s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }
  html.is-menu-vt::view-transition-old(menu-sheet) {
    animation: kinetic-sheet-out 0.28s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) both;
  }
}
/* Gentle tier: the sheet fades in or out where it stands. The group keeps no geometry animation
   (it would only exist if a sheet were captured on both sides), so nothing moves or resizes. */
@media not all { /* was: reduced motion (ignored by decision, see DESIGN.md) */
  html.is-menu-vt::view-transition-group(menu-sheet) { animation: none; }
  html.is-menu-vt::view-transition-new(menu-sheet) {
    animation: kinetic-fade-in 0.2s var(--fx-soft, cubic-bezier(0.33, 1, 0.68, 1)) both;
  }
  html.is-menu-vt::view-transition-old(menu-sheet) {
    animation: kinetic-fade-out 0.2s var(--fx-soft, cubic-bezier(0.33, 1, 0.68, 1)) both;
  }
}
@keyframes kinetic-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes kinetic-fade-out { from { opacity: 1; } to { opacity: 0; } }
/* The lower edge of the revealed sheet is the IWI diagonal (rising to the right, the same angle as
   the green fields); both corners travel together, so the angle holds for the whole wipe. */
@keyframes kinetic-sheet-in {
  from { clip-path: polygon(0 0, 100% 0, 100% calc(0% - var(--cut-wide, 11vw)), 0 0); }
  to { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% + var(--cut-wide, 11vw))); }
}
@keyframes kinetic-sheet-out {
  from { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% + var(--cut-wide, 11vw))); }
  to { clip-path: polygon(0 0, 100% 0, 100% calc(0% - var(--cut-wide, 11vw)), 0 0); }
}
