/* =========================================================================
 * /conformance/lab/ — the page that describes the instrument.
 *
 * Every diagram is inline SVG animated from here rather than from SMIL or a
 * script. Two reasons. The site's CSP forbids inline script and inline style
 * outright, so a scripted diagram is not an option; and a CSS animation is
 * the only one of the three that can be stopped and restarted on demand,
 * which the state class at the foot of this file does for all of them.
 *
 * The diagrams carry no information that is not also in the prose beside
 * them. Motion here is emphasis, never the message.
 * ====================================================================== */

.cf-lab h2 {
    margin-top: 2.4rem;
    font-size: 1.35rem;
    letter-spacing: -0.01em;
}

.cf-lab p {
    max-width: 74ch;
}

/* ------------------------------------------------------------- headline row */

.cf-labstats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(130px, 100%), 1fr));
    gap: 1px;
    margin: 1.6rem 0 0.4rem;
    background: var(--cf-border);
    border: 1px solid var(--cf-border);
    border-radius: 10px;
    overflow: hidden;
}

.cf-labstat {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.9rem 1rem;
    background: var(--cf-surface);
}

.cf-labstat-n {
    font-size: 1.7rem;
    font-weight: 600;
    line-height: 1;
    color: var(--cf-accent);
    font-variant-numeric: tabular-nums;
}

.cf-labstat-l {
    font-size: 0.76rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--cf-muted);
}

/* ---------------------------------------------------------------- figures */

.cf-fig {
    margin: 1.6rem 0 0.6rem;
    padding: 0;
}

.cf-fig figcaption {
    margin-top: 0.7rem;
    font-size: 0.84rem;
    color: var(--cf-ink-2);
    max-width: 74ch;
}

.cf-svg {
    display: block;
    width: 100%;
    height: auto;
    background: var(--cf-surface);
    border: 1px solid var(--cf-border);
    border-radius: 10px;
}

/* Shared SVG vocabulary. Declared once so a diagram is written in terms of
   what a shape means rather than what colour it is. */
.cf-svg-box {
    fill: var(--cf-surface-2);
    stroke: var(--cf-border);
    stroke-width: 1;
}

.cf-svg-box-accent {
    stroke: var(--cf-accent);
    fill: var(--cf-accent-soft);
}

.cf-svg-box-live {
    stroke: var(--cf-ext);
    fill: rgba(92, 193, 145, 0.12);
}

.cf-svg-wire {
    stroke: var(--cf-accent);
    stroke-width: 1.5;
    fill: none;
    opacity: 0.55;
}

.cf-svg-wire-dim {
    stroke: var(--cf-muted);
    opacity: 0.4;
}

.cf-svg-wire-back {
    stroke: var(--cf-ext);
    opacity: 0.5;
    stroke-dasharray: 4 4;
}

.cf-svg-cap {
    fill: var(--cf-ink);
    font-size: 13px;
    font-weight: 600;
}

.cf-svg-note {
    fill: var(--cf-ink-2);
    font-size: 11.5px;
}

.cf-svg-mono {
    fill: var(--cf-ink-2);
    font-family: var(--cf-mono);
    font-size: 12px;
}

.cf-svg-dim { fill: var(--cf-muted); }
.cf-svg-accent { fill: var(--cf-accent); }

.cf-svg-dgram {
    fill: var(--cf-accent);
}

/* --------------------------------------------------------------- ports */

.cf-svg-port {
    fill: var(--cf-surface-2);
    stroke: var(--cf-border);
}

.cf-svg-port-hot {
    stroke: var(--cf-accent);
    animation: cfLabPortGlow 3.6s ease-in-out infinite;
}

@keyframes cfLabPortGlow {
    0%, 78%, 100% { fill: var(--cf-surface-2); stroke-width: 1; }
    86%, 94%      { fill: var(--cf-accent-soft); stroke-width: 2.4; }
}

.cf-svg-port-hot { animation-duration: 1.9s; }

.cf-svg-ports .cf-svg-dgram {
    animation: cfLabTravel 1.9s linear infinite;
    filter: url(#labGlow);
}

.cf-svg-ports .cf-svg-dgram-b { animation-delay: 0.63s; }
.cf-svg-ports .cf-svg-dgram-c { animation-delay: 1.26s; }

@keyframes cfLabTravel {
    0%   { transform: translateX(0);     opacity: 0; }
    6%   { opacity: 1; }
    88%  { transform: translateX(222px); opacity: 1; }
    100% { transform: translateX(222px); opacity: 0; }
}

/* ---------------------------------------------------------------- tiers */

.cf-tier-pulse {
    fill: var(--cf-accent);
    opacity: 0;
    animation: cfLabPulse 2.4s ease-out infinite;
}

.cf-tier-t .cf-tier-pulse { animation-delay: 0s; }
.cf-tier-q .cf-tier-pulse { animation-delay: 0.8s; }
.cf-tier-h .cf-tier-pulse { animation-delay: 1.6s; }

@keyframes cfLabPulse {
    0%       { opacity: 0;    r: 6px; }
    10%      { opacity: 0.95; r: 6px; }
    34%      { opacity: 0;    r: 17px; }
    100%     { opacity: 0;    r: 17px; }
}

.cf-svg-tier-n {
    fill: var(--cf-accent);
    font-size: 26px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------ impairment pipe */

.cf-dg-drop {
    animation: cfLabDrop 2.6s ease-in infinite;
    filter: url(#labGlow);
}

@keyframes cfLabDrop {
    0%   { transform: translate(-112px, 0); opacity: 0; }
    12%  { opacity: 1; }
    55%  { transform: translate(0, 0);      opacity: 1; }
    80%  { transform: translate(10px, 30px); opacity: 0; }
    100% { transform: translate(10px, 30px); opacity: 0; }
}

/* The pair that arrives in the wrong order: B overtakes A. */
.cf-dg-a { animation: cfLabSwapA 2.6s ease-in-out infinite; filter: url(#labGlow); }
.cf-dg-b { animation: cfLabSwapB 2.6s ease-in-out infinite; fill: var(--cf-resil); filter: url(#labGlowWarm); }

@keyframes cfLabSwapA {
    0%   { transform: translate(-150px, 0); opacity: 0; }
    14%  { opacity: 1; }
    40%  { transform: translate(0, 0); }
    60%  { transform: translate(20px, 20px); }
    82%  { transform: translate(40px, 0); opacity: 1; }
    100% { transform: translate(190px, 0); opacity: 0; }
}

@keyframes cfLabSwapB {
    0%   { transform: translate(-190px, 0); opacity: 0; }
    14%  { opacity: 1; }
    40%  { transform: translate(0, 0); }
    60%  { transform: translate(-20px, -20px); }
    82%  { transform: translate(-40px, 0); opacity: 1; }
    100% { transform: translate(150px, 0); opacity: 0; }
}

.cf-dg-ce {
    animation: cfLabCe 2.6s linear infinite;
    filter: url(#labGlow);
}

@keyframes cfLabCe {
    0%   { transform: translateX(-190px); fill: var(--cf-accent); opacity: 0; }
    12%  { opacity: 1; }
    46%  { transform: translateX(0);      fill: var(--cf-accent); }
    56%  { fill: var(--cf-corr); }
    88%  { transform: translateX(200px);  fill: var(--cf-corr); opacity: 1; }
    100% { transform: translateX(200px);  opacity: 0; }
}

/* ------------------------------------------------------- verdict funnel */

.cf-obs-i {
    opacity: 0.45;
    animation: cfLabObs 5.2s linear infinite;
}


@keyframes cfLabObs {
    0%        { opacity: 0.4;  fill: var(--cf-ink-2); }
    4%, 9%    { opacity: 1;    fill: var(--cf-accent); }
    18%, 100% { opacity: 0.4;  fill: var(--cf-ink-2); }
}

.cf-judge-in,
.cf-judge-out {
    stroke-dasharray: 6 6;
    animation: cfLabDash 0.7s linear infinite;
}

@keyframes cfLabDash {
    to { stroke-dashoffset: -12; }
}

.cf-svg-chip { stroke-width: 1; }
.cf-svg-chiptext { font-size: 12px; font-weight: 600; fill: var(--cf-ink); }

.cf-verd-pass   { fill: rgba(92, 193, 145, 0.18); stroke: var(--cf-ext); }
.cf-verd-fail   { fill: rgba(240, 134, 141, 0.18); stroke: var(--cf-corr); }
.cf-verd-unsup  { fill: rgba(223, 176, 85, 0.16); stroke: var(--cf-resil); }
.cf-verd-incon  { fill: rgba(134, 151, 164, 0.16); stroke: var(--cf-incon); }
.cf-verd-notrun { fill: var(--cf-surface-2); stroke: var(--cf-border); }

/* ------------------------------------------------------------- read proof */

.cf-credit-out  { animation: cfLabCreditOut 3s ease-in-out infinite; filter: url(#labGlow); }
.cf-credit-back { fill: var(--cf-ext); animation: cfLabCreditBack 3s ease-in-out infinite; filter: url(#labGlowGreen); }

@keyframes cfLabCreditOut {
    0%        { transform: translateX(0);     opacity: 0; }
    5%        { opacity: 1; }
    44%       { transform: translateX(404px); opacity: 1; }
    50%, 100% { transform: translateX(404px); opacity: 0; }
}

@keyframes cfLabCreditBack {
    0%, 50%   { transform: translateX(0);     opacity: 0; }
    56%       { opacity: 1; }
    92%       { transform: translateX(-404px); opacity: 1; }
    97%, 100% { transform: translateX(-404px); opacity: 0; }
}

/* -------------------------------------------------------------- pipeline */

.cf-flow-1, .cf-flow-2, .cf-flow-3 {
    stroke-dasharray: 5 5;
    animation: cfLabDash 0.55s linear infinite;
}

.cf-flow-2 { animation-delay: 0.3s; }
.cf-flow-3 { animation-delay: 0.6s; }

/* ------------------------------------------------------------- fork cards */

.cf-forkgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
    gap: 1rem;
    margin-top: 1.2rem;
}

.cf-fork {
    padding: 1rem 1.1rem;
    background: var(--cf-surface);
    border: 1px solid var(--cf-border);
    border-left: 3px solid var(--cf-accent);
    border-radius: 10px;
    min-width: 0;
}

.cf-fork h3 {
    margin: 0 0 0.5rem;
    font-size: 0.95rem;
}

.cf-fork p {
    margin: 0;
    font-size: 0.86rem;
    color: var(--cf-ink-2);
}

/* ------------------------------------------------------------- the fleet */

.cf-fleet {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));
    gap: 0.7rem;
    margin-top: 1.2rem;
}

.cf-fleet-item {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.85rem;
    background: var(--cf-surface);
    border: 1px solid var(--cf-border);
    border-radius: 8px;
    text-decoration: none;
    min-width: 0;
    transition: border-color 0.18s ease, transform 0.18s ease;
}

.cf-fleet-item:hover,
.cf-fleet-item:focus-visible {
    border-color: var(--cf-accent);
    transform: translateY(-2px);
}

.cf-fleet-name {
    font-family: var(--cf-mono);
    font-size: 0.9rem;
    color: var(--cf-ink);
}

.cf-fleet-lang {
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--cf-accent);
}

.cf-fleet-stack {
    font-size: 0.74rem;
    color: var(--cf-muted);
    overflow-wrap: anywhere;
}

/* =======================================================================
 * Everything above is decoration. Turn all of it off on request, and make
 * that the last word so no later rule can reinstate it.
 * ==================================================================== */


/* =======================================================================
 * Blocks added with the deeper sections.
 * ==================================================================== */

.cf-svg-defs {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.cf-lab h3.cf-subhead {
    margin-top: 1.8rem;
    font-size: 1.05rem;
    color: var(--cf-ink);
}

.cf-labstats-tight .cf-labstat-n { font-size: 1.4rem; }

/* ------------------------------------------------- catalogue entry anatomy */

.cf-anatomy {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    gap: 1px;
    margin-top: 1.2rem;
    background: var(--cf-border);
    border: 1px solid var(--cf-border);
    border-radius: 10px;
    overflow: hidden;
}

.cf-anat {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.75rem 0.9rem;
    background: var(--cf-surface);
    min-width: 0;
}

.cf-anat code {
    font-family: var(--cf-mono);
    font-size: 0.82rem;
    color: var(--cf-accent);
}

.cf-anat span {
    font-size: 0.8rem;
    color: var(--cf-ink-2);
}

/* --------------------------------------------------------- class cards */

.cf-classgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: 0.9rem;
    margin-top: 1rem;
}

.cf-classcard {
    padding: 0.9rem 1rem;
    background: var(--cf-surface);
    border: 1px solid var(--cf-border);
    border-top: 3px solid var(--cf-border);
    border-radius: 10px;
    min-width: 0;
}

.cf-classcard h4 {
    margin: 0 0 0.4rem;
    font-family: var(--cf-mono);
    font-size: 0.86rem;
    letter-spacing: 0.02em;
}

.cf-classcard p {
    margin: 0;
    font-size: 0.82rem;
    color: var(--cf-ink-2);
}

.cf-classcard-correctness      { border-top-color: var(--cf-corr); }
.cf-classcard-interoperability { border-top-color: var(--cf-inter); }
.cf-classcard-extensibility    { border-top-color: var(--cf-ext); }
.cf-classcard-resilience       { border-top-color: var(--cf-resil); }
.cf-classcard-discretionary    { border-top-color: var(--cf-disc); }

.cf-classcard-correctness h4      { color: var(--cf-corr); }
.cf-classcard-interoperability h4 { color: var(--cf-inter); }
.cf-classcard-extensibility h4    { color: var(--cf-ext); }
.cf-classcard-resilience h4       { color: var(--cf-resil); }
.cf-classcard-discretionary h4    { color: var(--cf-disc); }

/* ------------------------------------------------------ attribution list */

.cf-labdl {
    margin-top: 1.2rem;
    max-width: 82ch;
}

.cf-labdl dt {
    color: var(--cf-ink);
    font-size: 0.88rem;
}

/* ------------------------------------------------------------ code block */

.cf-code {
    margin: 1.1rem 0 0.6rem;
    padding: 0.9rem 1.1rem;
    background: var(--cf-ground);
    border: 1px solid var(--cf-border);
    border-left: 3px solid var(--cf-accent);
    border-radius: 8px;
    overflow-x: auto;
}

.cf-code code {
    font-family: var(--cf-mono);
    font-size: 0.8rem;
    line-height: 1.7;
    color: var(--cf-ink-2);
    white-space: pre;
}

/* ------------------------------------------------------------- the limits */

.cf-limitgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
    gap: 0.9rem;
    margin-top: 1.2rem;
}

.cf-limit {
    padding: 0.9rem 1rem;
    background: var(--cf-surface);
    border: 1px solid var(--cf-border);
    border-left: 3px solid var(--cf-incon);
    border-radius: 10px;
    min-width: 0;
}

.cf-limit h4 {
    margin: 0 0 0.4rem;
    font-size: 0.9rem;
    color: var(--cf-ink);
}

.cf-limit p {
    margin: 0;
    font-size: 0.82rem;
    color: var(--cf-ink-2);
}

/* =======================================================================
 * Motion that reads as motion.
 *
 * The first version animated correctly and looked static, which is a real
 * distinction and the wrong side of it. Pixel-diffing two frames of each
 * figure 700ms apart put the change at 0.10%-2.8% of the area: a five-pixel
 * dot crossing a 720x200 canvas is technically travel and visually nothing.
 *
 * So the movers are large, they have trails, there are more of them, and the
 * big shapes take part too -- rows sweep, ports light in sequence, verdict
 * chips fire one after another. Everything here is still decoration and the
 * reduced-motion block at the foot of the file still stops all of it.
 * ==================================================================== */

/* Trails: a fading streak behind each packet, which multiplies the moving
   area by an order of magnitude for one extra element. */
.cf-svg-trail {
    fill: url(#labTrail);
    opacity: 0.32;
    transform-origin: left center;
    animation: cfLabTravel 1.9s linear infinite;
}

.cf-svg-trail-b { animation-delay: 0.63s; }
.cf-svg-trail-c { animation-delay: 1.26s; }

/* The whole port bank lights in a cascade, so the eye is led along it. */
.cf-p1, .cf-p2, .cf-p4, .cf-p5, .cf-p6, .cf-p7 {
    animation: cfLabPortSeq 1.9s ease-out infinite;
}

.cf-p1 { animation-delay: 0.05s; }
.cf-p2 { animation-delay: 0.15s; }
.cf-p4 { animation-delay: 0.35s; }
.cf-p5 { animation-delay: 0.45s; }
.cf-p6 { animation-delay: 0.55s; }
.cf-p7 { animation-delay: 0.65s; }

@keyframes cfLabPortSeq {
    0%, 55%, 100% { fill: var(--cf-surface-2); stroke: var(--cf-border); }
    68%           { fill: #16323b;             stroke: var(--cf-accent); }
}

/* A band that crosses each tier row: large area, unmistakable. */
.cf-tier-sweep {
    fill: url(#labSweep);
    opacity: 0;
    animation: cfLabSweep 2.4s ease-in-out infinite;
}

.cf-tier-t .cf-tier-sweep { animation-delay: 0s; }
.cf-tier-q .cf-tier-sweep { animation-delay: 0.8s; }
.cf-tier-h .cf-tier-sweep { animation-delay: 1.6s; }

@keyframes cfLabSweep {
    0%   { transform: translateX(0);     opacity: 0; }
    18%  { opacity: 0.3; }
    72%  { transform: translateX(554px); opacity: 0.3; }
    92%, 100% { transform: translateX(554px); opacity: 0; }
}

/* A continuous train through the impairment layer, so the wire is never bare. */
.cf-dg-flow {
    animation: cfLabFlow 2.6s linear infinite;
    opacity: 0.75;
}

.cf-dg-f1 { animation-delay: 0.85s; }
.cf-dg-f2 { animation-delay: 1.7s; }

@keyframes cfLabFlow {
    0%   { transform: translateX(-112px); opacity: 0; }
    10%  { opacity: 0.75; }
    90%  { transform: translateX(580px);  opacity: 0.75; }
    100% { transform: translateX(580px);  opacity: 0; }
}

/* The verdict chips fire in sequence rather than sitting still while the
   observations do all the work. */
.cf-chip-lit { animation: cfLabChip 5.2s ease-in-out infinite; }

.cf-verd-pass   { animation-delay: 0.2s; }
.cf-verd-fail   { animation-delay: 1.2s; }
.cf-verd-unsup  { animation-delay: 2.2s; }
.cf-verd-incon  { animation-delay: 3.2s; }
.cf-verd-notrun { animation-delay: 4.2s; }

@keyframes cfLabChip {
    0%, 14%, 100% { opacity: 0.55; }
    4%            { opacity: 1; }
}

/* A token walking the promotion chain end to end. */
.cf-pipe-token {
    animation: cfLabToken 4.4s ease-in-out infinite;
    filter: url(#labGlow);
}

@keyframes cfLabToken {
    0%        { transform: translateX(0);     opacity: 0; }
    6%        { opacity: 1; }
    22%       { transform: translateX(60px);  }
    38%       { transform: translateX(60px);  }
    54%       { transform: translateX(300px); }
    70%       { transform: translateX(300px); }
    88%       { transform: translateX(490px); opacity: 1; }
    100%      { transform: translateX(490px); opacity: 0; }
}

/* Wires carry a flowing dash everywhere, not only in the pipeline. */
.cf-svg-ports .cf-svg-wire,
.cf-svg-pipe .cf-svg-wire,
.cf-svg-credit .cf-svg-wire {
    stroke-dasharray: 7 7;
    animation: cfLabDash 0.6s linear infinite;
}

.cf-svg-credit .cf-svg-wire-back { animation-direction: reverse; }

/* Keep the reduced-motion escape hatch the last word. */

/* --- the three diagrams that were still reading as static ---------------- */

.cf-trail-f1 { animation: cfLabFlow 2.6s linear infinite; animation-delay: 0.85s; }
.cf-trail-f2 { animation: cfLabFlow 2.6s linear infinite; animation-delay: 1.7s; }

.cf-pipe-band {
    fill: url(#labSweep);
    opacity: 0;
    animation: cfLabPipeBand 2.6s ease-in-out infinite;
}

@keyframes cfLabPipeBand {
    0%   { transform: translateX(-40px); opacity: 0; }
    16%  { opacity: 0.26; }
    80%  { transform: translateX(560px); opacity: 0.26; }
    96%, 100% { transform: translateX(560px); opacity: 0; }
}

.cf-trail-out  { animation: cfLabCreditOut 3s ease-in-out infinite; }
.cf-trail-back { fill: url(#labTrailGreen); animation: cfLabCreditBack 3s ease-in-out infinite; }

/* Each stage of the promotion chain brightens as the token arrives in it. */
.cf-stagelit { animation: cfLabStage 4.4s ease-in-out infinite; }

.cf-stagelit-1 { animation-delay: 0s; }
.cf-stagelit-2 { animation-delay: 0.9s; }
.cf-stagelit-3 { animation-delay: 2.3s; }
.cf-stagelit-4 { animation-delay: 3.6s; }

@keyframes cfLabStage {
    0%, 22%, 100% { stroke-width: 1; }
    6%, 14%       { stroke-width: 3; }
}


@keyframes cfLabObsBand {
    0%   { transform: translateY(0); }
    100% { transform: translateY(260px); }
}

.cf-credit-band {
    fill: url(#labSweep);
    opacity: 0;
    animation: cfLabCreditBand 3s ease-in-out infinite;
}

@keyframes cfLabCreditBand {
    0%   { transform: translateX(0);     opacity: 0; }
    10%  { opacity: 0.26; }
    46%  { transform: translateX(404px); opacity: 0.26; }
    56%  { transform: translateX(404px); opacity: 0; }
    62%  { opacity: 0.2; }
    94%  { transform: translateX(0);     opacity: 0.2; }
    100% { transform: translateX(0);     opacity: 0; }
}

/* Chips change fill as they fire, not just opacity. */
@keyframes cfLabChip {
    0%, 14%, 100% { opacity: 0.5;  stroke-width: 1; }
    4%            { opacity: 1;    stroke-width: 2.6; }
}


/* =======================================================================
 * Stillness is a state, not a wall.
 *
 * This was a `prefers-reduced-motion: reduce` block that set
 * `animation: none !important`. It did exactly what it said, and the result
 * was the page's worst bug: on a machine with the preference set, the
 * background canvas and the cursor kept moving -- both are JavaScript and
 * neither consults the preference -- while all six diagrams sat dead, with
 * no control anywhere to bring them back. Reported as "the animation isn't
 * working", which it was not, for the reason I had written.
 *
 * So the preference now chooses the *initial* state (see js/lab-motion.js)
 * and this class is what holds it. `animation-play-state` rather than
 * `animation: none`, because it is reversible: the site's own motion button
 * flips it either way and remembers, which is the one thing the media query
 * could never do.
 * ==================================================================== */

.cf-lab-still *,
.cf-lab-still *::before,
.cf-lab-still *::after {
    animation-play-state: paused !important;
    transition: none !important;
}

/* Paused at 0%, the travelling shapes are mid-keyframe and several start at
   opacity 0. A still diagram has to still be a diagram, so they are placed
   where they read. */
.cf-lab-still .cf-svg-dgram { opacity: 1; }
.cf-lab-still .cf-obs-i     { opacity: 1; fill: var(--cf-ink-2); }
.cf-lab-still .cf-chip-lit  { opacity: 1; }
.cf-lab-still .cf-svg-trail,
.cf-lab-still .cf-tier-sweep,
.cf-lab-still .cf-pipe-band,
.cf-lab-still .cf-credit-band,
.cf-lab-still .cf-obs-band,
.cf-lab-still .cf-pipe-token,
.cf-lab-still .cf-dg-flow   { opacity: 0; }
.cf-lab-still .cf-stagelit  { stroke-width: 1; }

/* =======================================================================
 * Calm mode: no travel, but nothing goes dead either.
 *
 * The first attempt at this disabled every travelling and sweeping element
 * and claimed the colour changes would carry the diagrams. Counting the live
 * animations per figure showed what that actually produced:
 *
 *     ports 7 · tiers 3 · impairment 0 · verdicts 18 · read-proof 0 · pipeline 4
 *
 * Two diagrams with nothing animating at all, and two whose only motion was
 * a stroke or a text colour too slight to register. Reported, accurately, as
 * two diagrams working and the rest not.
 *
 * So a shape that may not travel is given something to do in place. Opacity
 * and colour only -- nothing here moves across the screen, which is what the
 * preference is about -- and the relay still runs left to right in time, so
 * the diagram reads in the same order it would if the packets flew.
 * ==================================================================== */

/* Nothing translates: the transforms are frozen and the sweeps hidden. */
.cf-lab-calm .cf-svg-trail,
.cf-lab-calm .cf-tier-sweep,
.cf-lab-calm .cf-pipe-band,
.cf-lab-calm .cf-credit-band,
.cf-lab-calm .cf-dg-flow {
    animation: none !important;
}

/* The wires stop flowing but do not stop meaning something: each brightens
   in its turn, which is the direction of the exchange, over a large enough
   area to register. Opacity only. */
@keyframes cfCalmWire {
    0%, 100% { opacity: 0.2; }
    45%      { opacity: 0.85; }
}

.cf-lab-calm .cf-svg-wire {
    animation: cfCalmWire 2.6s ease-in-out infinite !important;
    stroke-dasharray: none !important;
}

.cf-lab-calm .cf-svg-credit .cf-svg-wire      { animation-duration: 3s !important; }
.cf-lab-calm .cf-svg-credit .cf-svg-wire-back { animation-delay: 1.5s !important; }

.cf-lab-calm .cf-svg-trail,
.cf-lab-calm .cf-tier-sweep,
.cf-lab-calm .cf-pipe-band,
.cf-lab-calm .cf-credit-band,
.cf-lab-calm .cf-dg-flow {
    opacity: 0;
}

.cf-lab-calm .cf-obs-band {
    animation: none !important;
    opacity: 0;
}

/* In place of travel: each packet holds its position and pulses in turn, so
   the sequence is still legible as a sequence. */
@keyframes cfCalmBeat {
    0%, 100% { opacity: 0.12; }
    50%      { opacity: 1; }
}

.cf-lab-calm .cf-svg-dgram {
    transform: none !important;
    animation: cfCalmBeat 2.6s ease-in-out infinite !important;
}

.cf-lab-calm .cf-dg-drop    { animation-delay: 0s !important; }
.cf-lab-calm .cf-dg-a       { animation-delay: 0.5s !important; }
.cf-lab-calm .cf-dg-b       { animation-delay: 0.7s !important; }
.cf-lab-calm .cf-dg-ce      { animation-delay: 1.1s !important; }
.cf-lab-calm .cf-credit-out { animation-delay: 0s !important; }
.cf-lab-calm .cf-credit-back{ animation-delay: 1.3s !important; }
.cf-lab-calm .cf-pipe-token { opacity: 0; animation: none !important; }

/* The one packet the impairment layer throws away still reads as thrown
   away: it fades to nothing rather than falling off the wire. */
@keyframes cfCalmDrop {
    0%, 40% { opacity: 1; }
    75%     { opacity: 0; }
    100%    { opacity: 0; }
}

.cf-lab-calm .cf-dg-drop {
    animation: cfCalmDrop 2.6s ease-in-out infinite !important;
}

/* And the one that gets marked still changes colour where it sits. */
@keyframes cfCalmCe {
    0%, 35%   { fill: var(--cf-accent); opacity: 0.45; }
    55%, 85%  { fill: var(--cf-corr);   opacity: 1; }
    100%      { fill: var(--cf-accent); opacity: 0.45; }
}

.cf-lab-calm .cf-dg-ce {
    animation: cfCalmCe 2.6s ease-in-out infinite !important;
}

/* Verdict chips and pipeline stages were technically animating and visually
   not. In calm mode they are the whole show, so they carry it. */
.cf-lab-calm .cf-chip-lit {
    animation: cfCalmChip 5.2s ease-in-out infinite !important;
}

@keyframes cfCalmChip {
    0%, 20%, 100% { opacity: 0.35; }
    6%, 12%       { opacity: 1; }
}

.cf-lab-calm .cf-stagelit {
    animation: cfCalmStage 4.4s ease-in-out infinite !important;
}

@keyframes cfCalmStage {
    0%, 26%, 100% { opacity: 0.4; }
    6%, 18%       { opacity: 1; }
}

/* =======================================================================
 * A deliberate stop, when someone presses the button.
 * ==================================================================== */

.cf-lab-still *,
.cf-lab-still *::before,
.cf-lab-still *::after {
    animation-play-state: paused !important;
    transition: none !important;
}

.cf-lab-still .cf-svg-dgram { opacity: 1; }
.cf-lab-still .cf-obs-i     { opacity: 1; fill: var(--cf-ink-2); }
.cf-lab-still .cf-chip-lit  { opacity: 1; }
.cf-lab-still .cf-svg-trail,
.cf-lab-still .cf-tier-sweep,
.cf-lab-still .cf-pipe-band,
.cf-lab-still .cf-credit-band,
.cf-lab-still .cf-obs-band,
.cf-lab-still .cf-pipe-token,
.cf-lab-still .cf-dg-flow   { opacity: 0; }
.cf-lab-still .cf-stagelit  { stroke-width: 1; }

/* ------------------------------------------------- the reduction, redrawn */
/*
 * This was thirteen monospace lines in a column beside five chips, with a
 * band sliding down them and a lot of empty middle. It listed the variants
 * and taught nothing, because the interesting fact about the reduction is
 * not that there are thirteen of them.
 *
 * `judge()` short-circuits three: Unsupported, NotExercised and Violated
 * settle a cell whatever the test was. The other ten are matched against the
 * test's class and the error code its clause names, which is why the same
 * close is a pass on an extensibility port and a fail on a correctness one.
 * The card is now that shape: two paths in, one of them through the judging
 * step, and a fifth verdict that neither path reaches.
 */

.cf-obs-panel {
    fill: var(--cf-surface-2);
    stroke: var(--cf-border);
    stroke-width: 1;
}

.cf-obs-panel-direct {
    stroke: var(--cf-accent);
    stroke-dasharray: 3 3;
    opacity: 0.9;
}

.cf-obs-grouphead {
    fill: var(--cf-muted);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.cf-svg-verdict .cf-svg-mono { font-size: 11.5px; }

/* The two routes light in turn, so which observations skip the judging step
   is visible rather than merely stated. */
.cf-vwire {
    stroke-dasharray: 6 6;
    animation: cfLabDash 0.7s linear infinite, cfVwire 5.2s ease-in-out infinite;
}

@keyframes cfVwire {
    0%, 46%, 100% { opacity: 0.18; }
    10%, 34%      { opacity: 0.8; }
}

.cf-vwire-a { animation-delay: 0s, 0s; }
.cf-vwire-b { animation-delay: 0s, 0.25s; }
.cf-vwire-c { animation-delay: 0s, 0.5s; }
.cf-vwire-d { animation-delay: 0s, 2.4s; }
.cf-vwire-e { animation-delay: 0s, 2.9s; }
.cf-vwire-f { animation-delay: 0s, 3.1s; }
.cf-vwire-g { animation-delay: 0s, 3.3s; }

.cf-judge-box { animation: cfJudge 5.2s ease-in-out infinite; }

@keyframes cfJudge {
    0%, 40%, 100% { stroke-width: 1; }
    52%, 70%      { stroke-width: 2.6; }
}

/* Every observation name breathes gently rather than one at a time down a
   list, which is what the sliding band was compensating for. */
.cf-svg-verdict .cf-obs-i {
    opacity: 0.8;
    animation: cfObsBreathe 5.2s ease-in-out infinite;
}

.cf-obs-panel-direct ~ .cf-obs-i { animation-delay: 0.2s; }

@keyframes cfObsBreathe {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 0.95; }
}

.cf-lab-calm .cf-vwire {
    animation: cfVwire 5.2s ease-in-out infinite !important;
    stroke-dasharray: none !important;
}

/* -------------------------------------------------- diagrams on a phone */
/*
 * A viewBox scales its text with everything else, so at 390px the reduction
 * card rendered 358px wide and its 11.5px monospace came out under 6px —
 * present, and unreadable. The figures keep a legible floor and their own
 * wrapper takes the overflow, which is what `.cf-tablewrap` already does for
 * the wide tables. The page itself still never scrolls sideways.
 */
@media (max-width: 720px) {
    .cf-fig {
        overflow-x: auto;
        border-radius: 10px;
    }

    .cf-fig .cf-svg {
        min-width: 560px;
    }

    .cf-fig .cf-svg-verdict {
        min-width: 640px;
    }

    .cf-fig figcaption {
        position: sticky;
        left: 0;
    }
}

/* A limit the instrument has since narrowed rather than one it lives with.
   Marked, because "cannot see" and "sees by another route" are different
   claims and the section is only useful if it distinguishes them. */
.cf-limit-narrowed {
    border-left-color: var(--cf-resil);
}
