/*  plate-painted.css — opacity-based gating for the painted layer.
 *
 *  All `.painterly` groups inherit opacity from a single CSS variable
 *  --painted-opacity, set on :root by the slider input handler in
 *  index.html. At t = 0 painted layers are invisible (opacity 0); at
 *  t = 1 they're fully present (opacity 1). The slider drives the
 *  variable continuously across [0.5, 1.0] so the painted register
 *  fades in smoothly as the slider drifts toward the painted end.
 *
 *  Style references for each section appear inline. Add new painterly
 *  layers by giving them a `.painterly` class; the universal opacity
 *  rule below catches them automatically.                                 */

/* — Universal opacity gating ——————————————————————————— */

/*  Initial value lives on :root so it's available before the slider
 *  handler runs once. */
:root { --painted-opacity: 0; }

.plate-svg .painterly,
.plate-wrap > .painterly {
  opacity: var(--painted-opacity, 0);
  transition: opacity 180ms ease-out;
}
/*  When the painted register is fully off, also drop pointer-events on
 *  painterly groups so they don't intercept clicks while invisible. */
.plate-svg .painterly { pointer-events: none; }
.plate-wrap > .painterly { pointer-events: none; }

/*  Self-loop arcs in painted mode pick up a warmer ink so they read as
 *  part of the painted family rather than as a structural pencil line.
 *  Stays thin and light — self-relations are structural loops, not
 *  primary narrative threads.                                            */
.plate-svg.painted .self-loop-path {
  stroke: #8b6e3a;
  stroke-width: 0.8;
  opacity: 0.7;
}
.plate-svg.painted .self-loop-label {
  fill: #6b5230;
}

/*  Atmospheric washes.
 *  Reference: Caspar David Friedrich's atmospheric strata. Each register
 *  becomes a band of distinct quality of light, extended infinitely in
 *  the direction it should bleed (lifecycle: down; actor: right;
 *  decision: faded both up and down; realization: radial).
 *
 *  Atmosphere uses its OWN envelope (--atmosphere-opacity) — it ramps
 *  in alongside the rest of the painterly phase but then HOLDS at 1
 *  through the fresco phase. The fresco frames render on top of this
 *  living background rather than replacing it.                          */
.plate-svg .painterly-atmosphere {
  pointer-events: none;
  opacity: var(--atmosphere-opacity, 0);
  transition: opacity 220ms ease-out;
}

/* — Cartouche frame ————————————————————————————————————
 *
 *  An engraved doubled-rule frame around the colored area of the plate.
 *  The frame lives OUTSIDE the main plate SVG: it's a set of sibling
 *  SVG elements inside .plate-wrap, so the rectangles can fill the
 *  wrap's padding box (= the colored area) regardless of where the
 *  plate's viewBox letterboxes inside that box.
 *
 *  Layer cake:
 *    · .plate-frame-rects — one stretching SVG (preserveAspectRatio=
 *      "none", vector-effect="non-scaling-stroke") with the two
 *      concentric rule rectangles. Spans full container width AND
 *      height; uniform stroke widths despite anisotropic scaling.
 *    · .frame-glyph.fleuron — four corner SVGs (Unicode ❦), each with
 *      its own preserveAspectRatio="meet" so the glyph never squishes.
 *    · .frame-glyph.dingbat — four axial-midpoint SVGs (Unicode ❧),
 *      same trick.
 *
 *  Reference: 17th-century engraved frontispieces; printer's-flower
 *  cornerpieces; medieval manuscript ruling conventions.                  */

/*  Wrap is the positioning context for the absolutely-placed frame
 *  layers, and carries the painted-mode class that gates visibility.
 *  Frame edges align exactly with the colored area (the .plate-svg's
 *  box, which the atmosphere washes fill via huge userSpaceOnUse
 *  rects). No asymmetric insets — the visual character of "frame
 *  inside the work" comes from the corner ornaments + dingbat marks,
 *  not from a margin around the rule. This keeps the technique
 *  simple: one inset for everything, glyphs slot in at predictable
 *  offsets.                                                              */
.plate-wrap {
  position: relative;
  --frame-glyph-inset: 8px;   /* glyph offset from the colored-area edge */
}
/*  Wrap-scoped painterly visibility is now opacity-driven (see the
 *  universal rule at the top of this file). The old binary
 *  display:none/block toggle is gone.                                    */

/*  The frame sits inset from the container edge so the colored
 *  atmosphere washes (which fill the SVG element edge-to-edge via
 *  userSpaceOnUse + huge rects) are visible *past* the frame on every
 *  side. That margin is what makes the cartouche read as "the frame
 *  drawn ON the painted page" rather than "the frame drawn AROUND the
 *  painted area". Without it the colored area and the frame share the
 *  same boundary and the frame looks like a separator, not an
 *  ornament inside the work.                                            */
.plate-frame-rects {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  z-index: 2;
}
.plate-frame-rects .frame-outer {
  stroke: var(--reg-decision);
  stroke-width: 1.0;
  opacity: 0.55;
}
.plate-frame-rects .frame-inner {
  stroke: var(--reg-decision);
  stroke-width: 0.45;
  opacity: 0.42;
}

.plate-wrap .frame-glyph {
  position: absolute;
  pointer-events: none;
  overflow: visible;
  z-index: 3;
}
.plate-wrap .frame-glyph text {
  font-family: 'IM Fell English', var(--serif);
  fill: var(--reg-decision);
}
.plate-wrap .frame-glyph.fleuron {
  width: 28px;
  height: 28px;
}
.plate-wrap .frame-glyph.fleuron text {
  font-size: 17px;
  opacity: 0.62;
}
.plate-wrap .frame-glyph.dingbat {
  width: 22px;
  height: 22px;
}
.plate-wrap .frame-glyph.dingbat text {
  font-size: 13px;
  opacity: 0.50;
}

/*  Corner positions — fleurons sit just INSIDE the inner rule (the
 *  frame inset + a small extra step so the glyph doesn't touch the
 *  line). Rotation orients each fleuron toward the centre of the
 *  plate.                                                                */
.plate-wrap .fleuron.tl {
  top:  calc(var(--frame-top) + var(--glyph-inset));
  left: calc(var(--frame-x)   + var(--glyph-inset));
}
.plate-wrap .fleuron.tr {
  top:   calc(var(--frame-top) + var(--glyph-inset));
  right: calc(var(--frame-x)   + var(--glyph-inset));
  transform: rotate(90deg);
}
.plate-wrap .fleuron.br {
  bottom: calc(var(--frame-bottom) + var(--glyph-inset));
  right:  calc(var(--frame-x)      + var(--glyph-inset));
  transform: rotate(180deg);
}
.plate-wrap .fleuron.bl {
  bottom: calc(var(--frame-bottom) + var(--glyph-inset));
  left:   calc(var(--frame-x)      + var(--glyph-inset));
  transform: rotate(270deg);
}

/*  Axial dingbats — centered ON the outer rule itself (at the rule's
 *  inset value), so each dingbat reads as a small ornament *on* the
 *  rule, breaking its uninterrupted run with a flicker. The horizontal
 *  rules (top/bottom) live at the rule's vertical position, centered
 *  along the longer axis — the axis-midpoint is calculated from the
 *  frame's left/right insets so it's always halfway across the frame
 *  itself, even with asymmetric insets.                                  */
.plate-wrap .dingbat.top {
  top:  var(--frame-top);
  left: calc(50% + (var(--frame-x) - var(--frame-x)) / 2);
  transform: translate(-50%, -50%);
}
.plate-wrap .dingbat.bottom {
  bottom: var(--frame-bottom);
  left:   50%;
  transform: translate(-50%, 50%) rotate(180deg);
}
.plate-wrap .dingbat.left {
  left: var(--frame-x);
  top:  calc((var(--frame-top) + 100% - var(--frame-bottom)) / 2);
  transform: translate(-50%, -50%) rotate(270deg);
}
.plate-wrap .dingbat.right {
  right: var(--frame-x);
  top:   calc((var(--frame-top) + 100% - var(--frame-bottom)) / 2);
  transform: translate(50%, -50%) rotate(90deg);
}

/* — Painterly edges ———————————————————————————————————
 *
 *  When painted is on:
 *    · Bare edges fade to a draft beneath the painted layer (we keep them
 *      in DOM for hover/click handling — bare paths are still the
 *      pointer-event surface).
 *    · Painted edges render on top with kind-specific ink:
 *        - subclass: ink-black, calligraphic taper
 *        - primary:  golden thread, glow halo
 *        - faint:    graphite whisper
 *  Reference: William Blake (calligraphic ink); alchemical *coniunctio*
 *  (golden thread); Cy Twombly (graphite under-mark).                       */

/* Bare edges fade to a draft beneath the painted layer. */
.plate-svg.painted > .zoom-pan > .edges .edge .edge-path { opacity: 0.18; }
.plate-svg.painted > .zoom-pan > .edges .edge .edge-label { opacity: 0.30; }

/* Painted edges — visibility is governed by .painterly default rule above. */
.plate-svg .painterly-edges { pointer-events: none; }  /* bare edges catch clicks */

.plate-svg .painted-stroke { fill: none; }

/* Subclass — thin, light, dashed. Matches the bare-mode convention so
 * the "is-a" arrows read the same in both views: a quiet structural
 * relation, not a primary narrative line.                              */
.plate-svg .painted-edge-subclass .painted-stroke {
  stroke: #8b7d63;
  stroke-width: 0.7;
  stroke-linecap: round;
  stroke-dasharray: 1.4 3;
}

/* Primary — golden thread with glow halo. */
.plate-svg .painted-edge-primary .painted-stroke {
  stroke: #c9a961;
  stroke-width: 1.3;
  stroke-linecap: round;
  opacity: 0.95;
}
.plate-svg .painted-edge-primary .painted-halo {
  fill: none;
  stroke: #d4ba80;
  stroke-width: 4.5;
  opacity: 0.55;
  filter: url(#thread-glow);
}

/* Faint — graphite cross-cut, made more present so the relation reads
 * at rest. Solid (dashed treatment is reserved for "is-a" relations).
 * Always carries its label.                                              */
.plate-svg .painted-edge.faint .painted-stroke {
  stroke: #8b7d63;
  stroke-width: 0.75;
  opacity: 0.85;
}

/* Painted labels — italic, kind-tinted. */
.plate-svg .painted-label {
  font-family: var(--serif);
  font-style: italic;
  font-size: 11px;
  fill: var(--ink-2);
}
.plate-svg .painted-edge-primary .painted-label {
  fill: #8a6e2a;            /* gold-ink */
  font-weight: 500;
  /*  Fade out as the primary edge morphs onto its closest cartouche
   *  border — the edge becomes a straight line at the border, and
   *  there's nowhere for the predicate text to legibly sit. The label
   *  multiplies with the parent .painterly group's --painted-opacity
   *  so it picks up that envelope on top of this one.                  */
  opacity: calc(1 - var(--edge-border-morph, 0));
  transition: opacity 180ms ease-out;
}
.plate-svg .painted-edge-subclass .painted-label {
  fill: #1a1611;
}
.plate-svg .painted-edge.faint .painted-label {
  fill: var(--ink-3);
  font-size: 10px;
}

/* Hover/select: lift painted edges with the same accent treatment. */
.plate-svg .painted-edge.hl .painted-stroke {
  stroke: #b22222;
  stroke-width: 1.6;
  stroke-dasharray: none;
  opacity: 1;
}
.plate-svg .painted-edge.hl .painted-halo {
  stroke: #ce8e8e;
  opacity: 0.55;
}
.plate-svg .painted-edge.hl .painted-label {
  display: block;
  fill: #b22222;
}
.plate-svg .painted-edge.hl .painted-stroke[marker-end] {
  marker-end: url(#painted-arrow-accent);
}
.plate-svg .painted-edge.dim .painted-stroke,
.plate-svg .painted-edge.dim .painted-halo,
.plate-svg .painted-edge.dim .painted-label { opacity: 0.18; }

/* — Sigils as containers ————————————————————————————————
 *
 *  Sigils replace the bare rectangle frames in painted mode. Each sigil
 *  scales to ~1.8× the rect height so the emblem dominates visually;
 *  the class label sits centered on top, with a paper-halo around the
 *  letterforms so the text reads cleanly through any sigil strokes that
 *  pass behind.
 *  Reference: Kircher's combinatorial plates; Audubon natural-philosophy
 *  plates (specimen-emblem with name as caption); medieval alchemical
 *  glyphs; printer's-flower iconography.                                  */

/*  Bare rect frames fade OUT one-way as the aestheticized phase begins.
 *  Driven by --frame-rect-fade (a one-way envelope set in index.html that
 *  rises 0→1 over [0.18, 0.24] and stays at 1) so the rects don't reappear
 *  during the poeticized phase when --painted-opacity drops back to 0. */
.plate-svg .class-node.has-painted-sigil > .frame,
.plate-svg .class-node.faded > .frame {
  opacity: calc(1 - var(--frame-rect-fade, 0));
  transition: opacity 220ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .plate-svg .class-node.has-painted-sigil > .frame,
  .plate-svg .class-node.faded > .frame { transition: none; }
}

/* Painted keystone-outer — a slightly larger trapezoid wrapping the
 * Affordance keystone sigil. Dense dashed pattern reads as chisel
 * marks on carved stone; the stone-texture filter weathers the line
 * further.
 * Reference: architectural keystone with carved moulding; chiseled
 * voussoir surfaces.                                                    */
.plate-svg .painted-sigil-outer {
  fill: none;
  /*  Mix from the structural register tint to gold via --gold-tint, so
   *  the keystone outer ornament joins the colour shift along with the
   *  inner sigils. Same envelope as the per-register sigil rules below. */
  stroke: color-mix(in oklab,
                    var(--reg-structural),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  stroke-width: 0.9;
  /*  Sigils are leaf .painterly elements (no enclosing group). They use
   *  --sigil-opacity (decoupled from --painted-opacity) so they stay
   *  visible during the poeticized phase as they migrate to fresco
   *  positions, then fade out only at the very end of the slider.       */
  opacity: calc(var(--sigil-opacity, 0) * 0.6);
  stroke-dasharray: 1.4 0.9;
  filter: url(#stone-texture);
}
.plate-svg .class-node.hl .painted-sigil-outer,
.plate-svg .class-node.selected .painted-sigil-outer {
  stroke: var(--accent);
  opacity: calc(var(--sigil-opacity, 0) * 0.75);
  stroke-dasharray: none;
}

.plate-svg .painted-sigil {
  /* The <use> shadow-tree's path inherits stroke from here.
   * Default ink mixed with gold via --gold-tint — covers any sigil
   * that doesn't match a more specific register rule below
   * (e.g. SoftwareModule, Surface, Human, AIAgent).                    */
  fill: none;
  stroke: color-mix(in oklab,
                    var(--ink-2),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: calc(var(--sigil-opacity, 0) * 0.85);
  /*  Hand-drawn texture — subtle tremor along each stroke, in the
   *  manner of an engraving where every line carries the trace of the
   *  cutting tool. Reference: William Blake plate-engraving; Dürer
   *  woodcuts.                                                          */
  filter: url(#sigil-texture);
}

/* Painted-mode class labels: italic ET Book, register-tinted, with a
 * soft drop-shadow halo for legibility against busy sigil interiors.
 * No plaque — the labels float on the painted surface, the way the
 * already-italic Affordance keystone label and the faded genus labels
 * (Decision, Memory, Actor) already do. The plate stops alternating
 * between "painted" and "labeled" zones; it becomes one continuous
 * painting.
 *
 * Reference: italic-name-as-caption convention in botanical and
 * natural-philosophy plates (Audubon, Kircher, Linnaeus).               */

.plate-svg.painted .class-node .class-label {
  font-style: italic;
  letter-spacing: 0.025em;
  filter: drop-shadow(0 0 1.2px var(--paper))
          drop-shadow(0 0 0.6px var(--paper));
}

/*  Move labels BELOW their sigils as the painted register comes in —
 *  emblem-book caption convention. The sigil is the device above, the
 *  label is its name below. Translation tracks --sigil-position (the
 *  base sigil envelope WITHOUT the dim dip) so labels glide into
 *  caption position alongside the sigil and STAY there steady through
 *  the dim/rebrighten arc — using --sigil-opacity here would cause
 *  labels to bounce as the dip kicks in during compression.            */
.plate-svg .class-node.has-painted-sigil:not(.keystone) .class-label {
  transform: translateY(calc(var(--sigil-position, 0) * 32px));
  transition: transform 220ms ease-out, fill 220ms ease-out, font-style 220ms;
}
.plate-svg .class-node.has-painted-sigil.keystone .class-label {
  transform: translateY(calc(var(--sigil-position, 0) * 78px));
  transition: transform 220ms ease-out, fill 220ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .plate-svg .class-node.has-painted-sigil .class-label { transition: none; }
}

/* Register-tinted ink — labels take the color of their register,
 * matching the sigils. The label and sigil read as one visual family.   */
.plate-svg.painted .class-node[data-uri$="#Affordance"] .class-label,
.plate-svg.painted .class-node[data-uri$="#Techne"] .class-label,
.plate-svg.painted .class-node[data-uri$="#Signifier"]  .class-label,
.plate-svg.painted .class-node[data-uri$="#Bundle"]     .class-label {
  fill: var(--reg-structural);
}
.plate-svg.painted .class-node[data-uri$="#Decision"]     .class-label,
.plate-svg.painted .class-node[data-uri$="#Commitment"]   .class-label,
.plate-svg.painted .class-node[data-uri$="#Architecture"] .class-label,
.plate-svg.painted .class-node[data-uri$="#Design"]       .class-label,
.plate-svg.painted .class-node[data-uri$="#OpenQuestion"] .class-label {
  fill: var(--reg-decision);
}
.plate-svg.painted .class-node[data-uri$="#Seed"]           .class-label,
.plate-svg.painted .class-node[data-uri$="#Intent"]         .class-label,
.plate-svg.painted .class-node[data-uri$="#Session"]        .class-label,
.plate-svg.painted .class-node[data-uri$="#Artifact"]       .class-label,
.plate-svg.painted .class-node[data-uri$="#SoftwareModule"] .class-label,
.plate-svg.painted .class-node[data-uri$="#LiterateSpec"]   .class-label,
.plate-svg.painted .class-node[data-uri$="#Observation"]    .class-label,
.plate-svg.painted .class-node[data-uri$="#Desire"]         .class-label {
  fill: var(--reg-lifecycle);
}
.plate-svg.painted .class-node[data-uri$="#Actor"]       .class-label,
.plate-svg.painted .class-node[data-uri$="#Human"]       .class-label,
.plate-svg.painted .class-node[data-uri$="#AIAgent"]     .class-label,
.plate-svg.painted .class-node[data-uri$="#Surface"]     .class-label,
.plate-svg.painted .class-node[data-uri$="#Memory"]      .class-label,
.plate-svg.painted .class-node[data-uri$="#AgentMemory"] .class-label,
.plate-svg.painted .class-node[data-uri$="#HumanMemory"] .class-label {
  fill: var(--reg-actor);
}

/* Faded genus labels (Decision, Actor, Memory) gain small caps in
 * painted mode — they read as a *naming*, lighter than the regular
 * class-name labels. Italic small caps with extra letterspacing.        */
.plate-svg.painted .class-node.faded .class-label {
  font-variant: small-caps;
  letter-spacing: 0.10em;
  font-size: 14px;
}

/* Affordance drop cap — the keystone's first letter as a rubricated
 * initial in accent red. Single visual exception, not a rule. The
 * keystone is the axis mundi; rubricating its first letter marks it.   */
.plate-svg.painted .class-node[data-uri$="#Affordance"] .drop-cap {
  fill: var(--accent);
}

/* Hover/select state on the bare class-node propagates to the sigil
 * inside it — the sigil lights up in the brick-red accent, matching the
 * existing rect-frame highlight convention.                              */
.plate-svg .class-node.hl .painted-sigil,
.plate-svg .class-node.selected .painted-sigil {
  stroke: var(--accent);
  stroke-width: 1.5;
  opacity: var(--sigil-opacity, 0);
}
.plate-svg .class-node.dim .painted-sigil {
  opacity: calc(var(--sigil-opacity, 0) * 0.32);
}

/*  Special tints for unencodable-class sigils — faint, ghostly, italic.
 *  Reference: apophatic tradition (what is named by what cannot be seen).
 *  Still mix to gold via --gold-tint — they participate in the colour
 *  shift but from a fainter starting tint, ending pale-gold rather than
 *  full gold so they read as still-quieter than the others.            */
.plate-svg .painted-sigil[data-uri$="#Desire"],
.plate-svg .painted-sigil[data-uri$="#HumanMemory"] {
  stroke: color-mix(in oklab,
                    var(--ink-3),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  opacity: calc(var(--sigil-opacity, 0) * 0.45);
  stroke-dasharray: 1 1;
}

/*  Register-tinted sigils — match each register's color so the sigil
 *  reads as belonging to its band of being. Each rule keeps its
 *  designed 0.85 opacity but multiplies by --sigil-opacity so the
 *  sigil fades with its dedicated envelope (independent of the
 *  painterly-vector phase).
 *
 *  The stroke is a color-mix between the register tint and gold, with
 *  the gold percentage driven by --gold-tint (0 in non-fresco phases,
 *  1 once fresco is established). This produces a continuous, slider-
 *  driven colour transition from register to gold — no class-toggle
 *  needed, smooth in both directions.                                   */
.plate-svg .painted-sigil[data-uri$="#Affordance"],
.plate-svg .painted-sigil[data-uri$="#Techne"],
.plate-svg .painted-sigil[data-uri$="#Signifier"],
.plate-svg .painted-sigil[data-uri$="#Bundle"] {
  stroke: color-mix(in oklab,
                    var(--reg-structural),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  opacity: calc(var(--sigil-opacity, 0) * 0.85);
}
.plate-svg .painted-sigil[data-uri$="#Commitment"],
.plate-svg .painted-sigil[data-uri$="#Architecture"],
.plate-svg .painted-sigil[data-uri$="#Design"],
.plate-svg .painted-sigil[data-uri$="#OpenQuestion"] {
  stroke: color-mix(in oklab,
                    var(--reg-decision),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  opacity: calc(var(--sigil-opacity, 0) * 0.85);
}
.plate-svg .painted-sigil[data-uri$="#Seed"],
.plate-svg .painted-sigil[data-uri$="#Intent"],
.plate-svg .painted-sigil[data-uri$="#Session"],
.plate-svg .painted-sigil[data-uri$="#Artifact"],
.plate-svg .painted-sigil[data-uri$="#Observation"],
.plate-svg .painted-sigil[data-uri$="#LiterateSpec"] {
  stroke: color-mix(in oklab,
                    var(--reg-lifecycle),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  opacity: calc(var(--sigil-opacity, 0) * 0.85);
}
.plate-svg .painted-sigil[data-uri$="#AgentMemory"] {
  stroke: color-mix(in oklab,
                    var(--reg-actor),
                    #d4af37 calc(var(--gold-tint, 0) * 100%));
  opacity: calc(var(--sigil-opacity, 0) * 0.85);
}

/* — Fresco-arrived state: pulsing gold sigils ————————————
 *
 *  The colour shift (register → gold) is driven CONTINUOUSLY by the
 *  --gold-tint variable in the register CSS rules above (color-mix),
 *  so it animates smoothly with the slider in both directions — no
 *  class-toggle transition needed.
 *
 *  This rule only triggers the breathing pulse animation: filter halo
 *  + stroke-width oscillate gently while the fresco is on screen.      */
body[data-fresco-arrived] .plate-svg .painted-sigil {
  animation: sigil-gold-pulse 2.4s ease-in-out infinite;
}
body[data-fresco-arrived] .plate-svg .painted-sigil-outer {
  animation: sigil-gold-outer-pulse 2.4s ease-in-out infinite;
}

@keyframes sigil-gold-pulse {
  0%, 100% {
    filter: url(#sigil-texture) drop-shadow(0 0 1.5px #f5d96d) drop-shadow(0 0 0 transparent);
    stroke-width: 1.2;
  }
  50% {
    filter: url(#sigil-texture) drop-shadow(0 0 4px #f5d96d) drop-shadow(0 0 1.5px #fff5b3);
    stroke-width: 1.5;
  }
}

@keyframes sigil-gold-outer-pulse {
  0%, 100% { filter: url(#stone-texture) drop-shadow(0 0 1px #f5d96d); }
  50%      { filter: url(#stone-texture) drop-shadow(0 0 3px #f5d96d); }
}

/* — Sigil exit: bright burst on filter + stroke ——————————
 *
 *  At t ≥ 0.95 a one-shot burst animation overrides the gold pulse:
 *  the sigil flares with a wide gold/white halo and thicker stroke,
 *  then settles. Opacity is INTENTIONALLY NOT in the keyframes — fade
 *  out is driven by the --sigil-opacity envelope, which traverses
 *  symmetrically when the slider is reversed (so sigils fade back in
 *  smoothly when scrubbing from t=1 toward t=0.95).
 *  No `forwards` fill — when the class is removed the burst's filter/
 *  stroke-width effects revert to base values, allowing the gold-pulse
 *  to take over again if still in fresco-arrived state.                  */
body[data-sigil-exit] .plate-svg .painted-sigil {
  animation: sigil-exit-burst 1.4s ease-out !important;
}
body[data-sigil-exit] .plate-svg .painted-sigil-outer {
  animation: sigil-exit-burst 1.4s ease-out !important;
}

@keyframes sigil-exit-burst {
  0% {
    filter: drop-shadow(0 0 2px #f5d96d);
    stroke-width: 1.4;
  }
  35% {
    filter:
      drop-shadow(0 0 9px #fff5b3)
      drop-shadow(0 0 4px #f5d96d)
      drop-shadow(0 0 2px #fff8d8);
    stroke-width: 2.2;
  }
  100% {
    filter: drop-shadow(0 0 0.5px #f5d96d);
    stroke-width: 1.0;
  }
}

@media (prefers-reduced-motion: reduce) {
  body[data-fresco-arrived] .plate-svg .painted-sigil,
  body[data-fresco-arrived] .plate-svg .painted-sigil-outer,
  body[data-sigil-exit] .plate-svg .painted-sigil,
  body[data-sigil-exit] .plate-svg .painted-sigil-outer {
    animation: none !important;
  }
}

/* — Keystone corona ———————————————————————————————————
 *
 *  Soft mandorla halo behind Affordance. Breathes — opacity oscillates
 *  between 0.65 and 0.85 over 4 seconds. Suppressed under reduced-motion.
 *  Reference: medieval mandorla; alchemical *coniunctio*.                  */
.plate-svg .painterly-corona { pointer-events: none; }
.plate-svg.painted .painterly-corona .corona-halo {
  animation: corona-breathe 4s ease-in-out infinite;
}
@keyframes corona-breathe {
  0%, 100% { opacity: 0.65; }
  50%      { opacity: 0.95; }
}

/* — Memory vessels ————————————————————————————————————
 *
 *  In painted mode the AgentMemory and HumanMemory rectangle frames are
 *  hidden; vessel glyphs render in their place. The class label still
 *  shows on top of the vessel. AgentMemory's bowl is open, droplets
 *  visible; HumanMemory's bowl is misty, captioned with the apophatic
 *  qualifier.
 *  Reference: alchemical retort/alembic vessels; the apophatic
 *  tradition (what is named by what cannot be shown).                     */

/*  Memory rect frames fade out smoothly as the vessel glyphs come up,
 *  matching the .has-painted-sigil > .frame fade above. One-way fade
 *  driven by --frame-rect-fade so the rect doesn't reappear when the
 *  painterly phase ends.                                                 */
.plate-svg .class-node[data-uri$="#AgentMemory"] > .frame,
.plate-svg .class-node[data-uri$="#HumanMemory"] > .frame {
  opacity: calc(1 - var(--frame-rect-fade, 0));
  transition: opacity 220ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .plate-svg .class-node[data-uri$="#AgentMemory"] > .frame,
  .plate-svg .class-node[data-uri$="#HumanMemory"] > .frame { transition: none; }
}

.plate-svg .painterly-vessels { pointer-events: none; }

.plate-svg .vessel-bowl {
  fill: none;
  stroke: var(--reg-actor);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
}

/*  AgentMemory droplets — small filled pools, observations made visible.  */
.plate-svg .vessel-agent .vessel-droplet {
  fill: #a3b6cc;
  stroke: var(--reg-actor);
  stroke-width: 0.6;
  opacity: 0.78;
}

/*  HumanMemory mist — soft, wavy, never definite.                         */
.plate-svg .vessel-human .vessel-bowl {
  /* Slightly desaturated bowl rim for the human vessel. */
  stroke: var(--ink-3);
  stroke-dasharray: 1.2 0.6;
}
.plate-svg .vessel-human .vessel-mist {
  fill: none;
  stroke: var(--ink-quiet);
  stroke-width: 0.8;
  opacity: 0.5;
}

/*  Caption — italic small caps, soft. */
.plate-svg .vessel-caption {
  font-family: var(--serif);
  font-style: italic;
  font-size: 9.5px;
  fill: var(--ink-3);
  letter-spacing: 0.06em;
}

/* — Lifecycle path ——————————————————————————————————————
 *
 *  An earth-toned road through the lifecycle band. Two layers — a soft
 *  outer band, and a thinner ink core threading through it. Lifecycle
 *  nodes sit on the path as way-stations.
 *  Reference: medieval pilgrimage-route maps; Friedrich's landscape
 *  paths; Bunyan's *Pilgrim's Progress*.                                  */

.plate-svg .painterly-path { pointer-events: none; }
.plate-svg .lifecycle-path-band {
  fill: none;
  stroke: #a07a4a;
  stroke-width: 16;
  stroke-linecap: round;
  opacity: 0.20;
}
.plate-svg .lifecycle-path-core {
  fill: none;
  stroke: #6b4923;
  stroke-width: 1.0;
  stroke-linecap: round;
  opacity: 0.55;
  stroke-dasharray: 4 3;
}

/*  In painted mode the bare lifecycle separator (two horizontal hairlines)
 *  steps aside for the painted path.                                       */
.plate-svg.painted .lifecycle-rule { display: none; }

/* — Environments ————————————————————————————————————————
 *
 *  Each register's background is sketched as a low-opacity scenic hint:
 *  a great arch over the decision stratum, an apse cradling the
 *  realization keystone, grass tufts along the lifecycle path, a
 *  doorway threshold at the actor strip's far edge.
 *
 *  All scenery uses .env-stone or .env-grass classes — hairline weight,
 *  ~0.25-0.40 opacity, weathered by the stone-texture filter where
 *  appropriate. The eye reads the scenery as ambient context, not as
 *  foreground decoration.
 *  Reference: Kircher's architectural-natural-philosophy plates;
 *  medieval altarpiece architecture; Romantic doorway-as-portal.        */

.plate-svg .painterly-environments { pointer-events: none; }

.plate-svg .env-stone {
  fill: none;
  stroke: var(--ink-quiet);
  stroke-width: 0.55;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.40;
  filter: url(#stone-texture);
}
/* Outer arch curve — darker, registers as actual architecture. */
.plate-svg .env-arch-curve {
  opacity: 0.55;
  stroke-width: 0.7;
}
.plate-svg .env-arch-inner {
  opacity: 0.30;
  stroke-width: 0.45;
}
.plate-svg .env-apse-inner {
  opacity: 0.22;
  stroke-width: 0.4;
}
.plate-svg .env-threshold {
  opacity: 0.30;
}

/*  Grass tufts — slightly more saturated than env-stone (lifecycle
 *  green tone), small short strokes evoking growth.                       */
.plate-svg .env-grass {
  fill: none;
  stroke: var(--reg-lifecycle);
  stroke-width: 0.7;
  stroke-linecap: round;
  opacity: 0.50;
}

/*  Flowers — small asterisk marks scattered along the lifecycle band.
 *  Slightly warmer than the green tufts; reads as bloom rather than
 *  blade.                                                                 */
.plate-svg .env-flower {
  fill: none;
  stroke: #b8884a;
  stroke-width: 0.6;
  stroke-linecap: round;
  opacity: 0.55;
}

/* Title ornaments removed — the SVG no longer carries the title block.
 * The HTML masthead (in index.html) shows "L30 Product Ontology" once. */

/* — Reduced-motion guard ——————————————————————————————— */

@media (prefers-reduced-motion: reduce) {
  /*  Suppress all painted-mode animations and the cross-register
   *  fades. Slider-driven changes still apply, but they land instantly
   *  rather than animating across 180-300 ms.                            */
  .plate-svg.painted .painterly-corona .corona-halo {
    animation: none;
    opacity: 0.78;
  }
  .plate-svg .painterly,
  .plate-wrap > .painterly,
  .plate-svg .self-loop-path,
  .plate-svg .self-loop-label {
    transition: none !important;
  }
}
