/* ============================================================
   Ink clips -- family-moment doodles and bread-vibe elements.

   Source: pen drawings generated with the banana skill (Gemini),
   thresholded, then vectorised with vtracer (colormode: binary) into
   filled currentColor paths -- see prototype/v3/ink/*.svg. Each path
   carries a measured --len custom property (path length in user units,
   measured at build time in a headless browser via getTotalLength()).

   Motion: a path first draws on as a pen outline (stroke-dashoffset),
   then its fill settles in, staggered per path. This hooks into the
   SAME shared once-latch as the rest of v3 (js/v3-motion.js 6.4):
   elements carry [data-once] and the IntersectionObserver there flips
   is-waiting -> is-seen once, one way, the first time the element
   crosses 65% of the viewport. Nothing here adds a second observer.

   Tiers: the undrawn (is-waiting) state is written ONLY under
   html[data-motion="lite"] and "full". Under "none" and "lean" the
   is-waiting/is-seen class swap has no matching selector at all, so
   the finished drawing shows from the first frame -- the same pattern
   DESIGN.md's doodle draw and step4-motion.md 6.4 use for the later
   doodle-draw waiting style ("written under html[data-motion='full']
   ... on other tiers the class has no visible effect").

   Only transform, opacity and the stroke-dashoffset/fill-opacity paint
   exceptions already named in step4-motion.md 4.4 animate here.
   ============================================================ */

.ink-clip {
  margin: 0;
  color: var(--ink-text);
}
.ink-clip > svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Resting / finished state: a solid ink mark. This is what "none", "lean",
   no-JS and print all show, and what "lite"/"full" settle into once seen. */
.ink-draw path {
  fill: currentColor;
  fill-opacity: 1;
  stroke: currentColor;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--len, 1px);
  stroke-dashoffset: 0;
}

/* ---- Darker, bolder ink (2026-10-11 owner review). Root cause: these are filled trace
   shapes (vtracer output), so the hand-drawn line weight is mostly baked into the path
   geometry, not the CSS stroke -- 1.2 viewBox units of an 820 viewBox at a ~112-132px render
   is under 0.2 device px, so even --ink-text/--chalk's own near-black/near-white value
   anti-aliases to pale grey (no pixel ever reaches full alpha coverage). Two levers, measured
   on a clean test harness (six drawings: 03/10/14/16/17-ingredient icons, 06) and cross-checked
   against the live pages:
   (1) stroke-width above raised from 1.2 to 4: a measured, modest bump (not the 10-12+ that
       would be needed to fix the worst case below on its own) -- enough to meaningfully help
       the smallest renders without fusing fine detail shut (checked against 14-sourdough-
       starter-jar's small bubble rings and 03-toast-and-chai's steam swirl, both still read as
       open shapes at this width; bubbles start fusing into solid dots from stroke-width ~10).
   (2) contrast()/brightness() filter below on the rendered layer, which is where most of the
       work happens at normal render sizes.
   Combined, at ~112-132px (ingredient icons, inline doodles, all of process.html): darkest
   pixel 0/255 in light (target <=12), brightest pixel 251/255 in dark (target >=235),
   background unaffected in both (filter leaves fully-transparent pixels' alpha at 0, so it
   never paints a box around a drawing). brightness(0.65) only reads right in light: in dark it
   would dim the chalk strokes, so dark uses contrast() alone. Applies to both the resting SVG
   and the live raster loop (ink-live.js); the dark-theme .ink-live override below keeps this
   contrast() inside the SAME filter declaration as its existing invert/hue-rotate, since
   filter is one property and a second rule would just replace it.
   Known gap, not fully closed here: the footer .lineup row's own minimum width
   (`clamp(2.6rem, 9vw, 7.5rem)` in home.css/v3-port.css, not this agent's file) floors at
   41.6px under ~465px viewports. At that extreme size neither lever alone, nor this modest
   combination, reaches the targets for every drawing (see the report). Sizing that row is
   explicitly out of scope here (owned elsewhere, and already flagged in the doodle-theme study
   for a redraw); pushing stroke-width further to compensate was tested and rejected because it
   measurably fuses the jar's and other drawings' smallest closed shapes at normal render sizes. */
.ink-clip > svg,
.ink-live {
  filter: contrast(2.2) brightness(0.65);
}
html[data-theme="dark"] .ink-clip > svg {
  filter: contrast(1.2);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ink-clip > svg {
    filter: contrast(1.2);
  }
}

/* The drawn-on reveal exists only on tiers that pay for motion that still
   costs no bytes (DESIGN.md 4.6). Each path's stroke draws in, then its
   fill fades in 420ms after its own stroke starts, staggered by source
   order in 40ms steps for the first six paths (busier drawings share the
   6th step's delay, so even a 14-path trace stays inside the 900-1400ms
   total budget: 200ms max stagger + 900ms draw = 1100ms). */
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw path {
  transition: stroke-dashoffset 900ms var(--ease-expo),
              fill-opacity 500ms var(--ease-quart) 420ms;
}
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-waiting path {
  stroke-dashoffset: var(--len, 1px);
  fill-opacity: 0;
  transition: none;
}
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-seen path:nth-child(2) { transition-delay: 40ms, 460ms; }
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-seen path:nth-child(3) { transition-delay: 80ms, 500ms; }
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-seen path:nth-child(4) { transition-delay: 120ms, 540ms; }
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-seen path:nth-child(5) { transition-delay: 160ms, 580ms; }
html:is([data-motion="lite"], [data-motion="full"]) .ink-draw.is-seen path:nth-child(n + 6) { transition-delay: 200ms, 620ms; }

/* ---- Idle clips: steam and flour only, data-motion="full" only, and only
   once the drawing has finished its draw-on (is-seen). A subtle transform
   loop -- the one deliberate exception to "nothing moves on its own"
   (step4-motion.md 5 #1), scoped to these two decorative elements by the
   owner's brief. Flag for the owner: this is a named exception, same as
   the others logged in step4-motion.md 7, not a site-wide precedent. ---- */
@keyframes ink-idle-rise {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}
@keyframes ink-idle-drift {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  50%      { transform: translateX(3px) rotate(-2deg); }
}
html[data-motion="full"] .ink-idle--rise.is-seen {
  animation: ink-idle-rise 3400ms ease-in-out infinite;
}
html[data-motion="full"] .ink-idle--drift.is-seen {
  animation: ink-idle-drift 4200ms ease-in-out infinite;
}

/* Backups: a visitor who flips the OS setting after load, and print. */
@media print {
  .ink-draw path { stroke-dashoffset: 0 !important; fill-opacity: 1 !important; transition: none !important; }
  .ink-idle--rise, .ink-idle--drift { animation: none !important; }
}

/* ---- Inline modifier (Step 8 move 6): a clip set inside running text at
   icon scale, matching the reference's inline video embedding. The wrapper
   still carries .ink-clip (sizing from above); this only changes the box
   from block to an inline one sized off the current line-height. ---- */
.ink-clip--inline {
  display: inline-block;
  width: 1.5em;
  height: 1.5em;
  vertical-align: -0.3em;
  margin: 0 0.15em;
}

/* ---- Live layer (ink-live.js): a five-second transparent loop of the same
   drawing, laid over the finished SVG and crossfaded in over 600 ms once the
   draw-on has ended (frame 0 is the drawing, or close to it, so the handover reads
   as the drawing coming alive).
   lite and full only: the JS never creates it on none or lean, and the tier
   rule below keeps it out of the box should it ever be present there. ---- */
.ink-clip { position: relative; }

.ink-live {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms var(--ease-expo);
}

.ink-clip > svg { transition: opacity 600ms var(--ease-expo); }
.ink-clip.is-live > .ink-live { opacity: 1; }
.ink-clip.is-live > svg { opacity: 0; }

html:is([data-motion="none"], [data-motion="lean"]) .ink-live { display: none; }

/* Lamplight: the loop's ink is kitchen-ink #1a1814 baked into the pixels.
   invert(1) hue-rotate(180deg) turns it into #e9e7e3, within one level of
   chalk (#e8e6e2), so the loop matches the SVG it replaces. contrast(1.2) rides in the
   same declaration (2026-10-11 bolder-ink pass, see the .ink-clip > svg comment above) so
   the loop keeps pace with the now-darker/bolder resting SVG it crossfades with; filter is
   one property, so this can't be a second rule. */
html[data-theme="dark"] .ink-live { filter: invert(1) hue-rotate(180deg) contrast(1.2); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ink-live { filter: invert(1) hue-rotate(180deg) contrast(1.2); }
}

/* An ink clip that is a <span> (the ingredient legend on process.html) must be a block box:
   Firefox gives the absolutely placed live loop inside an inline box zero width, so the
   drawing vanished there once the loop took over (2026-10-16). Inline marks keep their own
   inline-block rule. */
span.ink-clip:not(.ink-clip--inline) { display: block; }

/* Lazy drawings (ink-lazy.js): the wrapper keeps its box before the svg arrives (aspect-ratio
   inline on the wrapper, or its own fixed size), so nothing shifts. No-JS gets the <noscript>
   <img>; a failed fetch gets the same <img class="ink-fb"> from the loader. */
.ink-clip noscript img,
.ink-clip > img.ink-fb { display: block; width: 100%; height: auto; }

/* No-JS and fetch-error fallbacks are plain <img> of the ink SVG (#1a1814 strokes). In the lamplight
   theme invert them so they read as chalk on dark paper, like the inline sprites do. */
html[data-theme="dark"] .ink-clip noscript img,
html[data-theme="dark"] .ink-clip > img.ink-fb { filter: invert(1); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ink-clip noscript img,
  :root:not([data-theme="light"]) .ink-clip > img.ink-fb { filter: invert(1); }
}
/* Handwritten margin marks and floating decorations are positioned by JS-era layout (percent offsets
   inside a field); without JS they float unattached, so they are not shown. */
html:not(.js) .mark,
html:not(.js) .float,
html:not(.js) .float-field { display: none; }
