/*
 * fusion: page-scoped rules for the technology group (/technology/ and
 * its five deep pages). Owned by IVW-025. Linked from every technology
 * page as /styles/pages-technology.css immediately after
 * /styles/variant.css, so a rule here wins over variant.css without
 * !important. IVW-023 created this file empty and wired the link;
 * IVW-025 appended page-scoped rules here and never edited variant.css.
 * Tokens only; no rotation; every animation added here carries a
 * reduced-motion static state.
 *
 * IVW-027 (coherence round 1) removed the deep-page crumb, replaced the
 * figure and claim-card measure caps with variant.css's .fu-measure and
 * folded the teaser card's layout into .fu-card. What remains is the
 * type-algebra diagram frame and the native-code target chips; IVW-038
 * added the hex byte storyboard (section 3), whose keyframes carry their
 * own reduced-motion static state below.
 */

/* 1. The set diagram frame on /technology/type-algebra/: the pane's
 * white surface and hairline instead of base.css's dashed --bg-2 frame,
 * and a caption on a solid strip so the mono text stays readable over
 * the drawing. */
.fu-diagram {
  background: var(--bg-0);
  border: 1px solid var(--code-border);
}

.fu-diagram .set-diagram {
  padding: var(--space-4);
}

.fu-diagram .diagram-frame__caption {
  background: var(--bg-0);
  border-top: 1px solid var(--code-border);
}

/* The error label is set in the drawing's user units, so it scales with
 * the drawing; at --text-sm it ran across both circle strokes at every
 * width (C4-4). It takes 0.7 of --text-xs and rises --space-16 plus
 * --space-2 in user units, to the band above the circles (their top is
 * at 25 units; the label's baseline lands at 13). Under the circles it
 * would sit behind the caption strip at 390 and touch it at 834.
 * translate composes with the shared keyframes' transform: scale() and
 * with the reduced-motion transform override, both untouched. */
.fu-diagram .set-diagram__error {
  font-family: var(--font-sans);
  font-size: calc(var(--text-xs) * 0.7);
  translate: 0 calc((var(--space-16) + var(--space-2)) * -1);
}

/* Below 40rem the caption runs to three lines and, as an overlay strip,
 * covered the drawing's lowest 26px (writer-found, W2). The frame drops
 * its ratio and the caption sits under the drawing in flow, as a
 * placeholder frame does on a phone (README 3.13). */
@media (max-width: 39.9375rem) {
  .fu-diagram {
    flex-direction: column;
    aspect-ratio: auto;
  }

  .fu-diagram .diagram-frame__caption {
    position: static;
  }
}

/* 2. Deployment chips on /technology/native-code/: chips on a hairline,
 * set in the body face (IVW-050 round two: in mono they read as code
 * tags under the examples exemption). */
.fu-targets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.fu-targets li {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  color: var(--fg);
  background: var(--bg-0);
  border: 1px solid var(--bg-3);
  border-radius: var(--space-1);
  padding: var(--space-2) var(--space-3);
}

/* 3. The hex byte storyboard on /technology/regular-languages/ (IVW-038):
 * the shared storyboard mechanism (motion.js: play, step, frame dots,
 * data-frame-active) with Scope-Forge in it. The pane shows all five
 * declarations; the active frame's line is lit and the others recede;
 * the frame below draws the symbolic automaton that line compiles to,
 * inline, labels in the mono face, all five frames on one 560 by 260
 * canvas so the board never changes height. The drawing keeps a 32rem
 * minimum and scrolls inside the viewport on a phone, as code does.
 *
 * Round one of the storyboard loop (IVW-039, STORYBOARD-REVIEW-1): the
 * figure is the containing block of its dots' hidden labels (D2); the
 * lit line bleeds exactly to the pre's padding edge at each width (D3);
 * the frame label is sticky at the viewport's content edge while the
 * drawing scrolls (D4); the viewport scrolls sideways only (D7); a
 * leaving frame is a cut (D8); from 40rem the viewport, controls and
 * caption share the pre's 24px inline padding (D10); below 40rem the
 * nine dots are one full-width row under Play and Step (D1), the bar
 * keeps its tag on one line (R12) and the viewport's right edge fades
 * as a scroll cue (R17). */
.fu-story {
  position: relative;
}

.fu-story__code {
  border-bottom: 1px solid var(--code-border);
}

.fu-story__line {
  display: block;
  margin-inline: calc(var(--space-4) * -1);
  padding-inline: var(--space-4);
  transition: color var(--duration-base) var(--ease-standard),
    background-color var(--duration-base) var(--ease-standard);
}

@media (min-width: 40rem) {
  .fu-story__line {
    margin-inline: calc(var(--space-6) * -1);
    padding-inline: var(--space-6);
  }

  .fu-story__viewport,
  .fu-story .fu-log__controls,
  .fu-story .cp__caption {
    padding-inline: var(--space-6);
  }

}

@media (max-width: 39.9375rem) {
  /* The nine frame buttons as a keypad of five and four (IVW-059 bug 6):
   * nine in one row were 32px wide at 390 and 28px at 360, under the
   * 44px a thumb needs, and nine 44px cells do not fit a 322px row. Five
   * tracks give 52 to 66px cells at 360 to 430, with the shared
   * --space-2 between them as on /ironidp/. */
  .fu-story .fu-log__dots {
    flex: 1 1 100%;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--space-2);
    margin-left: 0;
  }

  .fu-story .fu-log__dot {
    min-width: 0;
  }

  /* The declarations wrap on a phone as every other pane's code does
   * (variant.css section 10: that rule is scoped to .cp, and the
   * storyboard's pane is a .fu-log-frame, so its 547px of code was
   * clipped at the pane's edge, IVW-059 bug 2). The lit line is a block,
   * so its tint covers every wrapped line of the declaration. */
  .fu-story__code {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    overflow-x: visible;
  }

  .fu-story__code .code-block__code {
    font-size: var(--text-xs);
  }

  .fu-story .cp__tag {
    flex-basis: auto;
    margin-left: auto;
    text-align: right;
  }

  .fu-story__viewport {
    -webkit-mask-image: linear-gradient(to right, var(--fg) calc(100% - var(--space-6)), transparent);
    mask-image: linear-gradient(to right, var(--fg) calc(100% - var(--space-6)), transparent);
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--space-4);
  }

  .au {
    scroll-snap-align: start end;
  }
}

.fu-story__board[data-frame-active] .fu-story__line {
  color: var(--muted);
}

.fu-story__board[data-frame-active] .fu-story__line .tok-fn,
.fu-story__board[data-frame-active] .fu-story__line .tok-str,
.fu-story__board[data-frame-active] .fu-story__line .tok-kw,
.fu-story__board[data-frame-active] .fu-story__line .tok-type,
.fu-story__board[data-frame-active] .fu-story__line .tok-punct {
  color: inherit;
}





.fu-story__board[data-frame-active="1"] .fu-story__line[data-line="1"],
.fu-story__board[data-frame-active="2"] .fu-story__line[data-line="2"],
.fu-story__board[data-frame-active="3"] .fu-story__line[data-line="2"],
.fu-story__board[data-frame-active="4"] .fu-story__line[data-line="3"],
.fu-story__board[data-frame-active="5"] .fu-story__line[data-line="3"],
.fu-story__board[data-frame-active="6"] .fu-story__line[data-line="4"],
.fu-story__board[data-frame-active="7"] .fu-story__line[data-line="4"],
.fu-story__board[data-frame-active="8"] .fu-story__line[data-line="5"],
.fu-story__board[data-frame-active="9"] .fu-story__line[data-line="5"] {
  color: var(--fg);
  background: rgba(var(--brand-rgb), 0.1);
  box-shadow: inset 3px 0 0 var(--brand);
}

.fu-story__board[data-frame-active="1"] .fu-story__line[data-line="1"] .tok-fn,
.fu-story__board[data-frame-active="2"] .fu-story__line[data-line="2"] .tok-fn,
.fu-story__board[data-frame-active="3"] .fu-story__line[data-line="2"] .tok-fn,
.fu-story__board[data-frame-active="4"] .fu-story__line[data-line="3"] .tok-fn,
.fu-story__board[data-frame-active="5"] .fu-story__line[data-line="3"] .tok-fn,
.fu-story__board[data-frame-active="6"] .fu-story__line[data-line="4"] .tok-fn,
.fu-story__board[data-frame-active="7"] .fu-story__line[data-line="4"] .tok-fn,
.fu-story__board[data-frame-active="8"] .fu-story__line[data-line="5"] .tok-fn,
.fu-story__board[data-frame-active="9"] .fu-story__line[data-line="5"] .tok-fn {
  color: var(--fg);
}

.fu-story__viewport {
  overflow: auto hidden;
  min-height: 0;
}

.fu-story__viewport:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

.fu-story__frame {
  display: block;
}

/* A leaving frame is a cut: the result frame opens with the operands
 * as ghosts in the same positions, so a cross-fade would double them,
 * and the shared reverse of iv-frame-in only ever played from a frame
 * that had not itself entered (D8; the shared cause is follow-up F1).
 * visibility also drops the invisible drawing from the accessibility
 * tree and from hit testing. */
.fu-story__frame.is-frame-leaving {
  animation: none;
  opacity: 0;
  visibility: hidden;
}

/* The frame label sticks at the viewport's content edge while the
 * drawing scrolls on a phone (D4): a sticky inset is measured from the
 * scroll container's content box, so left: 0 is the code's own edge at
 * every width, at rest and scrolled. */
.fu-story__frame .fu-log__seq {
  position: sticky;
  left: 0;
  width: max-content;
  margin-bottom: var(--space-2);
}

.fu-story__name {
  color: var(--fg);
}

.au {
  display: block;
  width: 100%;
  min-width: 32rem;
  max-width: 40rem;
  height: auto;
  margin-inline: auto;
}

.au-edge {
  fill: none;
  stroke: var(--fg);
  stroke-width: 2;
  stroke-linecap: round;
}

.au-edge--dim {
  stroke: var(--bg-3);
}

.au-arrowhead {
  fill: var(--fg);
}

.au-arrowhead--dim {
  fill: var(--bg-3);
}

.au-state {
  fill: var(--bg-0);
  stroke: var(--fg);
  stroke-width: 2;
}

.au-state--accept-ring {
  fill: none;
  stroke: var(--brand);
}

.au-state--accept {
  stroke: var(--brand);
}

.au-state--reject {
  stroke: var(--bg-3);
}

.au-label {
  font-family: var(--font-mono);
  font-size: 12px;
  fill: var(--fg);
}

.au-label--dim {
  fill: var(--muted);
}

/* The operation plays as the frame enters (IVW-038, second pass). The
 * shared script adds .is-frame-entering to the frame it shows, so each
 * frame's parts carry a class for how they arrive: st-up rises onto the
 * edge (union: intervals join one transition), st-right slides in from
 * the right (concatenation: a machine is appended), st-strike draws a
 * rule through a label and st-out fades it (intersection: an interval
 * leaves the class), st-split-up and st-split-down move apart from one
 * point (exclusion: one state becomes two paths), st-fade appears, and
 * st-settle rises a step into its final place. Delays st-d1 to st-d4 are
 * two fast beats apart after the frame's own entrance. Without
 * .is-frame-entering (first paint, reduced motion, the dots used without
 * motion) every part sits in its end state; st-out parts end at opacity
 * 0 in both cases. */
.au-strike {
  stroke: var(--threat);
  stroke-width: 2;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: left center;
}

.st-out,
.au-strike {
  opacity: 0;
}

.au-g,
.au-state,
.au-label,
.au-edge {
  transform-box: fill-box;
  transform-origin: center;
}

@keyframes st-up {
  from { opacity: 0; transform: translateY(2.5em); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes st-right {
  from { opacity: 0; transform: translateX(80px); }
  to { opacity: 1; transform: translateX(0); }
}

@keyframes st-settle {
  from { transform: translateY(1.3em); }
  to { transform: translateY(0); }
}

@keyframes st-strike {
  0% { opacity: 1; transform: scaleX(0); }
  40% { opacity: 1; transform: scaleX(1); }
  100% { opacity: 0; transform: scaleX(1); }
}

@keyframes st-out {
  0% { opacity: 1; }
  60% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes st-split-up {
  from { transform: translateY(65px); }
  to { transform: translateY(0); }
}

@keyframes st-split-down {
  from { transform: translateY(-70px); }
  to { transform: translateY(0); }
}

.fu-story__frame.is-frame-entering .st-up { animation: st-up var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-right { animation: st-right var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-settle { animation: st-settle var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-strike { animation: st-strike calc(var(--duration-slow) * 1.5) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-out { animation: st-out calc(var(--duration-slow) * 1.5) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-split-up { animation: st-split-up var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-split-down { animation: st-split-down var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-fade { animation: lt-fade var(--duration-slow) var(--ease-standard) both; }

/* The st-d1 to st-d5 delays are declared once, after the last animation
 * shorthand in this file (the second structure below): a shorthand
 * resets animation-delay, so the delay block must follow every
 * shorthand it modifies, and one block after the last of them covers
 * them all (IVW-039; the file carried an identical, dead copy here). */

@media (prefers-reduced-motion: reduce) {
  .fu-story__frame .st-up,
  .fu-story__frame .st-right,
  .fu-story__frame .st-settle,
  .fu-story__frame .st-strike,
  .fu-story__frame .st-out,
  .fu-story__frame .st-split-up,
  .fu-story__frame .st-split-down,
  .fu-story__frame .st-fade {
    animation: none !important;
  }
}

/* Second structure (IVW-038, third pass): each operator takes two frames.
 * The operands frame draws both operand machines, one above the other,
 * with the operator in the margin between them (st-left and st-right
 * bring them in from their sides). The result frame opens with the same
 * two machines as ghosts, which move toward the centre and fade
 * (st-merge-down, st-merge-up; st-glue slides the second concatenation
 * operand onto the first's accepting state) while the result machine
 * emerges in their place (st-emerge). A struck label marks an interval
 * the product has dropped. Slides are in user units so a machine's
 * labels, states and edges travel the same distance (R14). Timing (D9):
 * the ghosts converge from 240 to 720ms and the result machine emerges
 * over the same beat (st-d1), so it is at half opacity while the ghosts
 * are still at half and the hand-off is a cross-over, not a gap; its
 * labels follow from 720 (st-d3), the note from 960 (st-d4); the frame
 * is at rest by 1440ms, before Play advances at 1600. The result at 480
 * (st-d2) was measured first: with the eased curves the result reached
 * half opacity only once the ghosts were at 0.01, a dim beat of about
 * 100ms where nothing on the canvas was above 0.1. */
.au-op {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 600;
  fill: var(--brand-text);
}

.au-label--struck {
  text-decoration: line-through;
  fill: var(--muted);
}

.st-ghost {
  opacity: 0;
}

.st-emerge {
  opacity: 1;
}

@keyframes st-left {
  from { opacity: 0; transform: translateX(-64px); }
  to { opacity: 1; transform: translateX(0); }
}

@keyframes st-merge-down {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(70px); }
}

@keyframes st-merge-up {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-72px); }
}

@keyframes st-glue {
  0% { opacity: 1; transform: translate(0, 0); }
  100% { opacity: 0; transform: translate(55px, -72px); }
}

@keyframes st-emerge {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: scale(1); }
}

.fu-story__frame.is-frame-entering .st-left { animation: st-left var(--duration-slow) var(--ease-standard) both; }
.fu-story__frame.is-frame-entering .st-merge-down { animation: st-merge-down var(--duration-slow) var(--ease-standard) calc(var(--duration-fast) * 2) both; }
.fu-story__frame.is-frame-entering .st-merge-up { animation: st-merge-up var(--duration-slow) var(--ease-standard) calc(var(--duration-fast) * 2) both; }
.fu-story__frame.is-frame-entering .st-glue { animation: st-glue var(--duration-slow) var(--ease-standard) calc(var(--duration-fast) * 2) both; }
.fu-story__frame.is-frame-entering .st-emerge { animation: st-emerge var(--duration-slow) var(--ease-standard) both; }

@media (prefers-reduced-motion: reduce) {
  .fu-story__frame .st-left,
  .fu-story__frame .st-merge-down,
  .fu-story__frame .st-merge-up,
  .fu-story__frame .st-glue,
  .fu-story__frame .st-emerge {
    animation: none !important;
  }
}

/* The delay classes follow every animation shorthand in this file: a
 * later animation shorthand resets animation-delay, so a delay rule must
 * come after every shorthand it modifies (the same cascade point as
 * variant.css entry 1). */
.fu-story__frame.is-frame-entering .st-d1 { animation-delay: calc(var(--duration-fast) * 2); }
.fu-story__frame.is-frame-entering .st-d2 { animation-delay: calc(var(--duration-fast) * 4); }
.fu-story__frame.is-frame-entering .st-d3 { animation-delay: calc(var(--duration-fast) * 6); }
.fu-story__frame.is-frame-entering .st-d4 { animation-delay: calc(var(--duration-fast) * 8); }
.fu-story__frame.is-frame-entering .st-d5 { animation-delay: calc(var(--duration-fast) * 10); }

/* Round two of the storyboard loop (IVW-039, STORYBOARD-REVIEW-2): the
 * right-edge fade must not dim the scroller's focus ring on a phone; a
 * step between the frame counter and the frame name; ghost accept rings
 * at reduced opacity so three orange rings do not stack during the
 * cross-over. */
@media (max-width: 39.9375rem) {
  .fu-story__viewport:focus-visible {
    -webkit-mask-image: none;
    mask-image: none;
  }
}

.fu-story__name {
  margin-inline-start: var(--space-1);
}

.st-ghost .au-state--accept-ring {
  opacity: 0.6;
}

/* 4. The scale figures on /technology/verification-boundary/ (IVW-050
 * round two, founder direction 7): the home proof strip carries six
 * items, each opening with a figure set at the 2xl step so the number is
 * the first thing read. From 48rem the strip is two rows of three and the
 * first item of each row starts on the column edge, as the strip's first
 * item does. */
.fu-scale__figure {
  display: block;
  font-size: var(--text-2xl);
  line-height: var(--leading-2xl, 1.1);
  letter-spacing: var(--tracking-2xl, 0);
  font-weight: 600;
  color: var(--fg);
  margin-bottom: var(--space-2);
}

.fu-scale .fu-proof__item p {
  max-width: 26ch;
}

/* The sixth figure is the brief's exact sentence, so it takes the xl step
 * and the item's full measure. */
.fu-scale__figure--sentence {
  font-size: var(--text-xl);
  line-height: var(--leading-xl, 1.25);
  letter-spacing: var(--tracking-xl, 0);
}

.fu-scale__note {
  margin-top: var(--space-6);
  font-size: var(--text-md, var(--text-base));
  color: var(--muted);
}

@media (min-width: 48rem) {
  .fu-scale .fu-proof {
    row-gap: var(--space-8);
  }

  .fu-scale .fu-proof__item:nth-child(3n + 1) {
    border-left: 0;
    padding-left: 0;
  }
}
