/* ============================================================================
   nblocks/animated-line — draw-on SVG lines, no animation library.

   The whole trick is one attribute on the <path>:

     pathLength="1"   the browser renormalises the path's length to 1, so every
                      dash value below is a fraction. No getTotalLength(), no JS
                      measurement, no relayout on resize.

   Everything else is CSS keyframes. view.js only adds one class when the
   element scrolls into view, or nothing at all in scroll-linked mode.

   STROKE WEIGHT — a deliberate choice. stroke-width here is in *viewBox units*,
   not screen pixels, so it thins as a stretched line scales down. The fix is
   the --nd-width ladder below (desktop / tablet / phone). The editor sets one
   weight; the tablet and phone steps are derived from it with fixed
   multipliers, and --nd-width-tablet / --nd-width-mobile are only written when
   a per-device override has been set. The alternative, vector-effect:
   non-scaling-stroke, renders inconsistently across browsers once
   preserveAspectRatio="none" is in play, which is exactly the case every
   full-width line here uses. Do not add it without re-testing Safari and
   Firefox.
   ========================================================================= */

/* --- the shape ---------------------------------------------------------- */

.nd-line {
	display: block;
	width: 100%;
	line-height: 0;
}

.nd-line > svg {
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;          /* round caps + loops must not be clipped */
}

.nd-line path {
	fill: none;
	stroke: var(--nd-stroke, currentColor);
	stroke-width: var(--nd-width, 5.5);
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* Responsive stroke ladder. The multipliers are the ratios of the old
   three-slider defaults (5.5 / 9 / 15), so one weight holds a steady visual
   thickness as the drawing scales down. */
@media (max-width: 900px) {
	.nd-line path { stroke-width: var(--nd-width-tablet, calc(var(--nd-width, 5.5) * 1.64)); }
}
@media (max-width: 600px) {
	.nd-line path { stroke-width: var(--nd-width-mobile, calc(var(--nd-width, 5.5) * 2.73)); }

	/* "Show on phones" off. A sweep laid behind a two-column section cuts
	   straight through the text once the columns stack. */
	.nd-line[data-hide-phone="true"] { display: none; }
}

/* --- the draw ------------------------------------------------------------

   A dash pattern of "<d> 2" with an offset of -a paints exactly the segment
   [a, a + d] of the path and nothing else. So every reveal below is just a
   linear tween of two numbers between a start pair and an end pair — no
   plugin required.

       segment [a,b]  ->  stroke-dasharray: (b - a) 2;
                          stroke-dashoffset: -a;

   The gap is 2, not 1, and that matters. pathLength normalises the path to 1,
   so a gap of exactly 1 makes the pattern repeat at position 1 — the path's
   own end — and a zero-length dash there paints a round dot, for the whole
   animation, at the end of a line that has not been drawn yet. A gap of 2
   puts the repeat out of reach and the dot never appears.
   ------------------------------------------------------------------------ */

@keyframes nd-forward  {           /* [0,0]   -> [0,1]   pen travels start to end */
	from { stroke-dasharray: 0 2;   stroke-dashoffset: 0;    }
	to   { stroke-dasharray: 1 2;   stroke-dashoffset: 0;    }
}
@keyframes nd-backward {           /* [1,1]   -> [0,1]   pen travels end to start */
	from { stroke-dasharray: 0 2;   stroke-dashoffset: -1;   }
	to   { stroke-dasharray: 1 2;   stroke-dashoffset: 0;    }
}
@keyframes nd-center   {           /* [.5,.5] -> [0,1]   grows both ways at once */
	from { stroke-dasharray: 0 2;   stroke-dashoffset: -0.5; }
	to   { stroke-dasharray: 1 2;   stroke-dashoffset: 0;    }
}
@keyframes nd-half-back {          /* [.5,.5] -> [0,.5]  first half, drawn backwards */
	from { stroke-dasharray: 0 2;   stroke-dashoffset: -0.5; }
	to   { stroke-dasharray: 0.5 2; stroke-dashoffset: 0;    }
}
@keyframes nd-half-forward {       /* [.5,.5] -> [.5,1]  second half, drawn forwards */
	from { stroke-dasharray: 0 2;   stroke-dashoffset: -0.5; }
	to   { stroke-dasharray: 0.5 2; stroke-dashoffset: -0.5; }
}

/* --- wiring --------------------------------------------------------------
   Hidden from the very first paint. This has to be a plain rule, not one
   gated on a class JS adds: style.css is in <head> but view.js is in the
   footer, so a gated rule lets the finished line paint once before it is
   hidden — a flash of the whole line on every load.

   The cost is that a reader with JavaScript off would see nothing, so
   init.php prints a <noscript> block that puts the line back.
   ------------------------------------------------------------------------ */

.nd-line[data-draw] path { visibility: hidden; }

.nd-line[data-draw].is-drawn path {
	visibility: visible;
	animation-name: nd-forward;
	animation-duration: var(--nd-duration, 3s);
	animation-timing-function: var(--nd-ease, cubic-bezier(0.42, 0, 0.20, 1));
	animation-delay: calc(var(--nd-delay, 0s) + var(--i, 0) * var(--nd-stagger, 0.18s));
	animation-fill-mode: both;
}

.nd-line[data-draw="backward"].is-drawn     path { animation-name: nd-backward; }
.nd-line[data-draw="center"].is-drawn       path { animation-name: nd-center; }
.nd-line[data-draw="half-back"].is-drawn    path { animation-name: nd-half-back; }
.nd-line[data-draw="half-forward"].is-drawn path { animation-name: nd-half-forward; }

/* --- scroll-linked mode --------------------------------------------------
   Where the browser supports scroll-driven animations the line is scrubbed by
   the scroll position and JS drops out of the picture entirely.

   The range is entry 100% -> exit 0%, so it starts the moment the whole line
   is on screen and finishes as its top edge reaches the top of the window —
   which means it is always exactly half drawn when the line is centred,
   whatever the screen size.
   ------------------------------------------------------------------------ */

@supports (animation-timeline: view()) {
	.nd-line[data-mode="scroll"] path {
		visibility: visible;
		animation-name: nd-forward;
		animation-duration: auto;
		animation-timing-function: linear;
		animation-delay: 0s;
		animation-fill-mode: both;
		animation-timeline: view();
		animation-range: var(--nd-range, entry 100% exit 0%);
	}
	.nd-line[data-mode="scroll"][data-draw="backward"]     path { animation-name: nd-backward; }
	.nd-line[data-mode="scroll"][data-draw="center"]       path { animation-name: nd-center; }
	.nd-line[data-mode="scroll"][data-draw="half-back"]    path { animation-name: nd-half-back; }
	.nd-line[data-mode="scroll"][data-draw="half-forward"] path { animation-name: nd-half-forward; }
}

/* --- placement -----------------------------------------------------------

   In flow: the line is its own row, and owns a height. Overlay: it is taken
   out of flow and positioned inside the nearest container.
   ------------------------------------------------------------------------ */

/* Height is a real length, in both placements, and it is the ONLY thing that
   stretches the drawing. Leave it unset and the SVG sizes from its own aspect
   ratio, so the line cannot distort at all; set it and the line stretches to
   fill, which is the point of preserveAspectRatio="none" — one drawing that
   spans any width without getting taller as the window grows.

   What is deliberately NOT here is a height as a percentage of the section.
   That was unpredictable: the same line squashed or stretched every time the
   section's content changed height. To make a line genuinely flatter or
   taller, change the Height — that redraws the curve in a new box instead of
   squeezing the old one. */
.nd-line {
	height: var(--nd-height, auto);
}

.nd-line[data-placement="overlay"] {
	position: absolute;
	pointer-events: none;
	left: 0;

	/* !important for the same reason as the max-width and margin resets below:
	   the theme's editor-only assets/css/editor-style.css carries
	     .alignfull:not(.is-layout-constrained) > * { width: 100%; ... }
	   at the same (0,2,0) specificity as this rule and is injected into the
	   canvas after a block's own style.css, so a plain `width` here loses in
	   the editor and wins on the front end — a line that published edge to edge
	   but drew short of it in the canvas. */
	width: 100% !important;

	/* Undo core's constrained layout. It puts
	     max-width: <content-size>; margin-left: auto !important; margin-right: auto !important
	   on every direct child of the container that is not aligned wide or full —
	   and this block is a direct child of exactly that container, because that
	   is what an overlay positions itself against.

	   Both halves break an out-of-flow box. The max-width clamps a line that is
	   meant to run wider than the content column, and on an absolutely
	   positioned box with `left` and `width` set and `right: auto`, an `auto`
	   inline margin resolves to 0 — which silently discards the centring
	   margin in the span rules below and leaves the line starting at the
	   middle of the section.

	   !important is the only thing that beats !important, and the reset is
	   scoped to the inline axis so the block's margin support still works. */
	max-width: none !important;
	margin-inline: 0 !important;
}

/* Vertical anchor. Each of these keeps the line's own height in the reckoning,
   which "top: 80%" never did — that put the line's top edge near the bottom of
   the section and let the rest hang out below it. "bottom" means the bottom of
   the line meets the bottom of the section, whatever the line's height. */
.nd-line[data-anchor="top"] {
	top: var(--nd-offset, 0%);
}
.nd-line[data-anchor="middle"] {
	top: calc(50% + var(--nd-offset, 0%));
	transform: translateY(-50%);
}
.nd-line[data-anchor="bottom"] {
	top: auto;
	bottom: calc(-1 * var(--nd-offset, 0%));
}

/* Behind: above the container's own background, below every in-flow sibling —
   which is only true because of the isolation: isolate below. In front: over
   the lot. */
.nd-line[data-layer="behind"] { z-index: -1; }
.nd-line[data-layer="front"]  { z-index: 3; }

/* Any container holding an overlay line becomes a positioned stacking context,
   so the editor never has to remember to set one.

   isolation: isolate is the load-bearing half. Without it a z-index of -1
   escapes to an ancestor stacking context and the line vanishes behind the
   section's own background instead of sitting on top of it.

   Note we do NOT set overflow here, unlike the is-style-ambient-* rule in
   main.css: overflow: hidden silently kills position: sticky in descendants,
   and the theme ships a Sticky style for core/group and core/column. Clipping
   is the [data-span="overhang"] rule below, and uses clip rather than hidden. */
:where(.wp-block-group, .wp-block-cover, .wp-block-columns, .wp-block-column, .wp-block-media-text):has(> .nd-line[data-placement="overlay"]) {
	position: relative;
	isolation: isolate;
}

/* --- span: how wide the line runs ---------------------------------------

   The same four words in both placements: column, wide, full, past the
   edges. In flow, wide and full are core's own alignwide / alignfull classes
   (see lineClassName() in src/lib/renderLine.js), so the container's
   constrained layout — and its root-padding-aware negative margins — do the
   work. Column is the default every other block gets. Only "past the edges"
   needs a rule of its own in flow.

   As an overlay the line is out of flow, so all four are measured here,
   against the section it is laid over.

   translate, NOT margin-left, for centring. Centring an absolutely positioned
   box with `left: 50%` needs a pull-back of half its own width, and margin is
   exactly the property that core's constrained layout, root-padding-aware
   alignment and the alignfull class all write to. translate is written by
   nothing else in the cascade, and its percentage is of the element's own
   box, which is the half-width we want. It is a separate property from
   `transform`, so it composes with the middle anchor's translateY rather than
   colliding with it. */
.nd-line[data-placement="overlay"][data-span="column"],
.nd-line[data-placement="overlay"][data-span="wide"] {
	left: 50%;
	translate: -50% 0;
}
.nd-line[data-placement="overlay"][data-span="column"] {
	width: min(100%, var(--wp--style--global--content-size, 100%)) !important;
}
.nd-line[data-placement="overlay"][data-span="wide"] {
	width: min(100%, var(--wp--style--global--wide-size, 100%)) !important;
}

/* Past the edges.

   --nd-overhang is a width, not a flag, and that is the whole fix for "it will
   not start and end off screen": at exactly 100% the two round end caps sit on
   the section's edges, in full view. Over 100% they are pushed past, and the
   line reads as passing through rather than stopping.

   A PERCENTAGE OF THE SECTION, NOT vw. On a full-width section the two are the
   same number on the front end, but only there. In the canvas the section is
   the canvas, and how the canvas relates to `vw` depends on whether the editor
   is iframed, zoomed, or previewing a device — the line used to land in a
   different place under each. The section is the one thing it is actually
   laid over, so measure against that.

   An in-flow line keeps its row: relative positioning shifts the box without
   touching the row it occupies, and nothing else in the cascade writes to
   `left`. The zero inline margin is there because core's `auto !important`
   would otherwise turn into a centring that is wrong for anything wider than
   the column (auto margins on an over-wide box resolve to zero, not to
   centred — CSS 2.1 §10.3.3). */
.nd-line[data-span="overhang"] {
	left: 50%;
	width: var(--nd-overhang, 120%) !important;
	translate: -50% 0;
}
.nd-line[data-placement="flow"][data-span="overhang"] {
	position: relative;
	max-width: none !important;
	margin-inline: 0 !important;
}

/* The overhang is clipped by the section it runs past, always. There is no
   version of "run off both edges" that wants a horizontal scrollbar, so this
   is not a choice the editor is asked to make. overflow: clip rather than
   hidden: clip does not create a scroll container, so a Sticky child of the
   same section keeps working. */
:where(.wp-block-group, .wp-block-cover, .wp-block-columns, .wp-block-column, .wp-block-media-text, .entry-content):has(> .nd-line[data-span="overhang"]) {
	overflow: clip;
}

/* --- fine-tune: start and width -------------------------------------------

   Only at the column span, which is the only one that does not already own
   the line's width. In flow the row is kept and the box slides within it; as
   an overlay both numbers are measured across the section from its left edge,
   so a negative start pushes the first end cap out of sight. */
.nd-line[data-placement="flow"][data-offset="true"] {
	position: relative;
	max-width: none !important;
	margin-inline: 0 !important;
	width: var(--nd-w, 100%) !important;
	left: var(--nd-left, 0%);
}
.nd-line[data-placement="overlay"][data-offset="true"] {
	left: var(--nd-left, 0%);
	width: var(--nd-w, 100%) !important;
	translate: 0 0;
}

/* The theme's editor-only stylesheet gives every child of an aligned,
   non-constrained element `max-width: <wide>; width: 100%; margin: auto`. A
   line with an alignment class is exactly that element, and its child is the
   drawing — which then gets capped at the wide size and centred inside a box
   that was meant to be wider than it. The drawing always fills its box. */
.nd-line[data-draw] > svg {
	max-width: none;
	margin: 0;
}

/* Flips are on the <svg>, not the wrapper, so they never collide with the
   translateY the middle anchor puts on the wrapper. */
.nd-line[data-flip-h="true"] > svg { transform: scaleX(-1); }
.nd-line[data-flip-v="true"] > svg { transform: scaleY(-1); }
.nd-line[data-flip-h="true"][data-flip-v="true"] > svg { transform: scale(-1, -1); }

/* --- respect the setting ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	.nd-line path {
		visibility: visible !important;
		animation: none !important;
		stroke-dasharray: none !important;
		stroke-dashoffset: 0 !important;
	}
}

@media print {
	.nd-line path {
		visibility: visible !important;
		animation: none !important;
		stroke-dasharray: none !important;
		stroke-dashoffset: 0 !important;
	}
}
