/* Kortlek base: the one place a card's transform, edge and light are composed.
   Modules (src/fx-*.js) write variables or classes, never `transform` or `box-shadow`.
   Layered: unlayered rules (a skin, a page, a recipe override) always win without !important. */

@layer navet-base, kortlek;

/* Typed variables: they can transition when no spring drives them, and calc() always has a number. */
@property --k-z  { syntax: "<number>"; inherits: false; initial-value: 0; }   /* lift progress, 0 rest .. 1 awake (springs overshoot) */
@property --k-p  { syntax: "<number>"; inherits: false; initial-value: 0; }   /* press travel, 0 up .. 1 bottomed out */
@property --k-rx { syntax: "<number>"; inherits: false; initial-value: 0; }   /* tilt, degrees */
@property --k-ry { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --k-fx { syntax: "<number>"; inherits: false; initial-value: 0; }   /* field displacement, px */
@property --k-fy { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --k-mx { syntax: "<number>"; inherits: false; initial-value: 0.5; } /* pointer in the card, 0..1 */
@property --k-my { syntax: "<number>"; inherits: false; initial-value: 0.5; }
@property --k-f  { syntax: "<number>"; inherits: false; initial-value: 0; }   /* field strength on this card, 0..1 */
@property --k-back { syntax: "<number>"; inherits: false; initial-value: 0; } /* CSS-only step back, px: the trappa recipe sets it, no script */

@layer kortlek {
  /* ---- knob defaults. Override on a card, a grid, a page or a recipe. Today's hub feel = these numbers. */
  :root {
    --k-lift: 3px;           /* wake lift, up and left */
    --k-edge: 4;             /* extrusion steps, 0..6 */
    --k-tilt: 8deg;          /* max tilt (yaw is x1.125) */
    --k-tilt-follow: 1;      /* 0 = no pointer tilt */
    --k-spring-k: 170;       /* stiffness */
    --k-spring-c: 22;        /* damping: critical at 2*sqrt(k*m) = 26; 22 (ratio 0.84) overshoots 0.46%, which reads as none */
    --k-spring-m: 1;         /* mass; the spring's feel is k and c, leave m at 1 */
    --k-t-idle: 640ms;       /* awake this long before idle() runs */
    --k-t-enter: 380ms;      /* the grid's scan-in, per card */
    --k-t-release: 420ms;    /* how long .k-released stays after a release */
    --k-sink: 1px;           /* press travels this far BELOW rest (letterpress); 0 = flush */
    --k-travel: 4;           /* key travel in edge steps */
    --k-act: 2;              /* actuation point, in steps of travel */
    --k-reset: 1.5;          /* reset point (hysteresis): below actuation */
    --k-t-attack: 90ms;      /* press: time to 95% down (within 11%) */
    --k-rebound: 0.5;        /* release spring damping ratio (zeta): 1 = none, .3 = bouncy */
    --k-t-hold: 900ms;       /* peak-hold tick on leave */
    --k-velocity: 1;         /* pointer speed -> press thud, 0 = off */
    --k-stamp-rot: -2deg;    /* the stamp's tilt */
    --k-t-stamp: 160ms;      /* the stamp shows at least this long */
    --k-sheen: 0.5;          /* cursor spotlight strength, 0 = off */
    --k-sheen-size: 220px;   /* the spotlight's diameter */
    --k-foil: 0;             /* foil strength, 0 = off */
    --k-reach: 2.2;          /* field reach in grid cells */
    --k-push: 8px;           /* field displacement at the card next to the pointer */
    --k-quant: 2px;          /* displacement is rounded to this step (20px = the row stairs) */
    --k-t-wave: 70ms;        /* wake wave: delay per grid cell of distance */
    --k-t-stagger: 40ms;     /* enter: delay per card, capped at 640ms in total */
    --k-hum: 1;              /* ambient strength while awake, 0 = off */
    --k-wear-rate: 1;        /* 0 = this card never remembers */
    --k-rare: 1;             /* multiplier on every rare odds, 0 = never */
    --k-persp: 900px;        /* perspective of the live card's 3D */
  }

  /* ---- composition. A card opts in with data-fx (or the class k-card).
     At rest a card is a flat 2D translate: no compositor layer, and a field or trappa neighbour that is asleep still moves
     through it. While core's .k-live is on (awake, pressed, or settling until its springs rest) the same translate is
     composed in 3D with tilt, preserve-3d and the strip's depth. With no script there is no .k-live: .is-awake still lifts
     the card through the 2D translate, without tilt or the strip's float.
     Measured 2026-10-20 (Chromium LayerTree): about 6 compositor layers at rest whatever the card count (4 to 7 on these
     pages, plus Patrick's two canvases on his); one awake card adds 8 to 23 depending on the card. Once it settles no card
     and no card child keeps a layer of its own; Chromium keeps one document scrolling layer after the first wake, and
     Patrick's cat canvas one overlap layer. With 3D at rest it was about 7.6 per card. */
  :is(.k-card, [data-fx]) {
    --k-es: max(0, calc(var(--k-edge, 4) * var(--k-z, 0) * (1 - var(--k-p, 0))));   /* visible edge steps; a thud past bottom-out never flips the edge */
    --k-up: calc(var(--k-lift, 3px) * var(--k-z, 0) * (1 - var(--k-p, 0)));     /* lift above rest, px */
    --k-dn: calc(var(--k-sink, 1px) * var(--k-p, 0) + var(--k-back, 0) * 1px);   /* sink below rest, px (press, or a neighbour stepping back) */
    --e1: var(--silv-2); --e2: var(--silv-3); --e3: var(--silv-4); --e4: var(--silv-5); --e5: var(--e4); --e6: var(--e4);
    transform: translate(calc(var(--k-fx, 0) * 1px - var(--k-up) + var(--k-dn)), calc(var(--k-fy, 0) * 1px - var(--k-up) + var(--k-dn)));
    box-shadow:
      calc(min(var(--k-es), 1) * 1px) calc(min(var(--k-es), 1) * 1px) 0 var(--e1),
      calc(min(var(--k-es), 2) * 1px) calc(min(var(--k-es), 2) * 1px) 0 var(--e2),
      calc(min(var(--k-es), 3) * 1px) calc(min(var(--k-es), 3) * 1px) 0 var(--e3),
      calc(min(var(--k-es), 4) * 1px) calc(min(var(--k-es), 4) * 1px) 0 var(--e4),
      calc(min(var(--k-es), 5) * 1px) calc(min(var(--k-es), 5) * 1px) 0 var(--e5),
      calc(min(var(--k-es), 6) * 1px) calc(min(var(--k-es), 6) * 1px) 0 var(--e6),
      0 0 28px -8px color-mix(in srgb, var(--e1) calc(var(--neon, 0) * 100% * min(var(--k-z, 0), 1)), transparent);
  }
  :is(.k-card, [data-fx]).k-live {
    transform-style: preserve-3d; will-change: transform;
    transform:
      perspective(var(--k-persp, 900px))
      translate3d(calc(var(--k-fx, 0) * 1px - var(--k-up) + var(--k-dn)), calc(var(--k-fy, 0) * 1px - var(--k-up) + var(--k-dn)), 0)
      rotateX(calc(var(--k-rx, 0) * 1deg)) rotateY(calc(var(--k-ry, 0) * 1deg));
  }
  :is(.k-card, [data-fx]).tk-card--free { --e1: var(--gold-2); --e2: var(--gold-3); --e3: var(--gold-4); --e4: var(--gold-5); --e5: var(--gold-5); --e6: var(--gold-5); }
  /* Dag: step 4 is already decoration (1.27:1 on void), so steps 5 and 6 are dropped rather than repeat it. Same specificity
     as the free rule and after it; the patrick recipe (css/recipes.css, later) keeps its own fifth step. */
  [data-theme="dag"] :is(.k-card, [data-fx]) { --e5: transparent; --e6: transparent; }
  :is(.k-card, [data-fx]).k-live .tk-strip { --k-z: inherit; transform: translateZ(calc(16px * var(--k-z, 0))); }   /* --k-z is not inherited by default (@property) */

  /* No script: the class the hub already sets still lifts the card, by a plain transition of --k-z.
     When lift drives z with a spring it sets data-k-js and that transition steps aside; --k-p keeps its 80 ms transition
     unless keycap drives it too (css/fx-press.css). --t-travel 440ms must stay under core.js's LIVE_MS (600). */
  :is(.k-card, [data-fx]) { transition: --k-z var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), --k-p 80ms linear, --k-back var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), background-color var(--t-blink, 80ms) linear, color var(--t-blink, 80ms) linear, border-color var(--t-blink, 80ms) linear; }
  :is(.k-card, [data-fx]).is-awake { --k-z: 1; }
  :is(.k-card, [data-fx]).is-pressed { --k-p: 1; }
  :is(.k-card, [data-fx])[data-k-js] { transition: --k-p 80ms linear, --k-back var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), background-color var(--t-blink, 80ms) linear, color var(--t-blink, 80ms) linear, border-color var(--t-blink, 80ms) linear; }   /* lift's spring drives z */

  /* Layers a module may append (aria-hidden, no pointer events). Light sits above the surface, below the text. */
  .k-layer { position: absolute; inset: 0; pointer-events: none; overflow: hidden; border-radius: 0; z-index: 0; }
  :is(.k-card, [data-fx]) { position: relative; }   /* no isolation/overflow/filter here: they flatten preserve-3d */
  :is(.k-card, [data-fx]) > :not(.k-layer) { position: relative; z-index: 1; }

  /* State words ([armed], [near], [wear n], the stamp): one type spec, a word in a square 1px box, never colour alone.
     Each is an aria-hidden span (core.js stateWord, the stamp layer), so it never joins the link's accessible name.
     font-stretch 75% is the hub's; the state-word type token has no field for it. A module shows its word with a class.
     The type rule is the one copy, shared with Patrick's mjau (at its own 12px line) and Triptych's [stäng]. */
  .k-word, .k-layer.k-stamp, .k-pat-mjau, .tp-close {
    font: 600 11px/16px var(--font-mono, ui-monospace, monospace); font-stretch: 75%; letter-spacing: .04em;
  }
  .k-word, .k-layer.k-stamp {
    box-sizing: border-box; padding: 0 var(--q1, 4px); border: 1px solid currentColor; border-radius: 0; white-space: nowrap;
  }
  .k-word { display: none; align-items: center; }

  /* Reduced motion: no springs, no tilt, no displacement, no lift. The lit state stays: the border, the stepped edge, the words.
     (The hub today sets .is-awake { transform: none } under reduced motion; this is the same promise.) Tilt and displacement
     are springs that write inline values, so core snaps them to 0 itself (also when the setting changes live). */
  @media (prefers-reduced-motion: reduce) {
    :is(.k-card, [data-fx]), :is(.k-card, [data-fx])[data-k-js] { transition: none; --k-lift: 0px; --k-sink: 0px; --k-back: 0; }
    :is(.k-card, [data-fx]).k-live .tk-strip { transform: none; }
  }
  /* The same, switched by hand (the Design System's motion toggle sets data-k-motion="off" on <html>). */
  :root[data-k-motion="off"] :is(.k-card, [data-fx]), :root[data-k-motion="off"] :is(.k-card, [data-fx])[data-k-js] { transition: none; --k-lift: 0px; --k-sink: 0px; --k-back: 0; }
  :root[data-k-motion="off"] :is(.k-card, [data-fx]).k-live .tk-strip { transform: none; }

  /* Windows high contrast removes box-shadow and forces border colours: the awake and pressed states get an outline, and
     pressed a dashed one, so neither depends on the stepped edge or on colour. */
  @media (forced-colors: active) {
    :is(.k-card, [data-fx]).is-awake { outline: 2px solid Highlight; outline-offset: 2px; }
    :is(.k-card, [data-fx]).is-pressed { outline-style: dashed; }
  }
}
/* Kortlek fx-wake: the CSS side of lift, sheen and foil (src/fx-wake.js). tilt has none: base.css composes --k-rx / --k-ry.
   The layers are appended by script, aria-hidden, pointer-events none (.k-layer in base.css). Colours come from Navet's
   tokens only (--ink, --e1..--e4); the fallbacks are currentColor, never a literal. */
@layer kortlek {
  /* --k-z is registered with inherits:false in base.css, so a child sees 0 unless it asks for the card's value */
  .k-sheen, .k-foil { --k-z: inherit; }

  /* ---- sheen: a spotlight. Opacity = strength * lift progress; the gradient square only ever translates.
     Strength is capped at .5: above it the pool under the slug takes ink-faint text below 4.5:1 (natt 4.49 at .6). */
  .k-sheen {
    --_sheen-ink: var(--k-sheen-ink, color-mix(in srgb, var(--ink, currentColor) 16%, transparent));
    opacity: calc(min(var(--k-sheen, 0.5), 0.5) * clamp(0, var(--k-z, 0), 1));
  }
  .k-sheen > i {
    position: absolute; left: 0; top: 0;
    width: var(--k-sheen-size, 220px); height: var(--k-sheen-size, 220px);
    margin: calc(var(--k-sheen-size, 220px) / -2) 0 0 calc(var(--k-sheen-size, 220px) / -2);
    transform: translate(calc(var(--k-sx, 0) * 1px), calc(var(--k-sy, 0) * 1px));   /* 2D: no layer at rest; -live promotes it */
    background: radial-gradient(closest-side, var(--_sheen-ink), color-mix(in srgb, var(--_sheen-ink) 40%, transparent) 45%, transparent);
  }
  .k-sheen-live { will-change: opacity; }
  .k-sheen-live > i { will-change: transform; }

  /* ---- foil: bands from the card's own edge ramp, soft-light, 300% x 300% so the parallax never shows an edge.
     The ramp climbs e1..e4 and back, one 120px period, so the bands read as brushed metal and not as stripes. */
  .k-foil {
    --_f1: color-mix(in srgb, var(--e1, currentColor) 22%, transparent);
    --_f2: color-mix(in srgb, var(--e2, currentColor) 22%, transparent);
    --_f3: color-mix(in srgb, var(--e3, currentColor) 22%, transparent);
    --_f4: color-mix(in srgb, var(--e4, currentColor) 22%, transparent);
    mix-blend-mode: soft-light;
    opacity: calc(var(--k-foil, 0) * clamp(0, var(--k-z, 0), 1));
  }
  .k-foil > i:first-child {
    position: absolute; left: -100%; top: -100%; width: 300%; height: 300%;
    transform: translate(calc(var(--k-gx, 0) * 1px), calc(var(--k-gy, 0) * 1px));   /* 2D: no layer at rest; -live promotes it */
    background: repeating-linear-gradient(115deg,
      transparent 0, var(--_f1) 16px, var(--_f2) 32px, var(--_f3) 46px, var(--_f4) 60px,
      var(--_f3) 74px, var(--_f2) 88px, var(--_f1) 104px, transparent 120px);
  }
  .k-foil-live { will-change: opacity; }
  .k-foil-live > i:first-child { will-change: transform; }

  /* the rare glint: one pale band, animated by script (Web Animations, transform only), parked off the left edge */
  .k-foil > .k-foil-glint {
    position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
    transform: translateX(-130%);
    background: linear-gradient(115deg, transparent, color-mix(in srgb, var(--e1, currentColor) 40%, transparent) 50%, transparent);
  }

  /* Windows high contrast: decorative light would only muddy the forced colours. The border still shows the state. */
  @media (forced-colors: active) {
    .k-sheen, .k-foil { display: none; }
  }
}
/* Kortlek fx-press: SLAGLANGD (keycap), TRYCK (stamp), HAPTIC (haptic).
   Never writes `transform` or `box-shadow` on the card: --k-p (a spring) and base.css compose those.
   Only the stamp and the tick, which are children, carry their own transform / border. */

@layer kortlek {
  :root {
    --k-haptic: 0;           /* 1 = vibrate 8 ms at release (Android only, needs user activation) */
    --k-click: 0;            /* 1 = a 6 ms click at release (WebAudio, after a gesture) */
  }

  /* ---- SLAGLANGD: the key's travel is a spring on --k-p, so base.css's 80 ms linear transition on --k-p
     would lag it. Keep the --k-z transition (a keycap without lift still rises on wake) and --k-back, drop the --k-p one. */
  :is(.k-card, [data-fx])[data-fx~="keycap"] { transition: --k-z var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), --k-back var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), background-color var(--t-blink, 80ms) linear, color var(--t-blink, 80ms) linear, border-color var(--t-blink, 80ms) linear; }
  :is(.k-card, [data-fx])[data-fx~="keycap"][data-k-js] { transition: --k-back var(--t-travel, 440ms) var(--e-travel, cubic-bezier(.2, 0, 0, 1)), background-color var(--t-blink, 80ms) linear, color var(--t-blink, 80ms) linear, border-color var(--t-blink, 80ms) linear; }
  @media (prefers-reduced-motion: reduce) {
    :is(.k-card, [data-fx])[data-fx~="keycap"], :is(.k-card, [data-fx])[data-fx~="keycap"][data-k-js] { transition: none; }
  }
  :root[data-k-motion="off"] :is(.k-card, [data-fx])[data-fx~="keycap"] { transition: none; }

  /* Actuated: the state is a bracketed word (a .k-word span, type in base.css), never colour alone. It joins the "->" row
     after the arrow, 18px in a 20px line. */
  .k-armed .k-word-armed { display: inline-flex; color: var(--ink); }

  /* Peak-hold: a 2px tick on the right edge, at peak/travel of the card's height. A border, so it survives forced-colors. */
  .k-layer.k-peakmark {
    inset: auto; right: -1px;
    top: calc(clamp(0, var(--k-peak, 0) / var(--k-travel, 4), 1) * (100% - 2px));
    width: 7px; height: 0; border-top: 2px solid currentColor; border-radius: 0;
    overflow: visible; opacity: 0;
  }
  .k-tick > .k-peakmark { opacity: 1; }

  /* ---- TRYCK: the stamp is a child, so it may carry its own transform. It appears in one frame (the class), never fades.
     Type in base.css (.k-word). Flat at rest, so a print left after a leave is no compositor layer; while the card is live
     it stands 24px * --k-z forward, in front of the strip (16px * --k-z), and its depth eases out with the lift, so it is
     already flat when .k-live drops. */
  .k-layer.k-stamp {
    inset: 8px 8px auto auto; z-index: 2; overflow: visible; visibility: hidden; --k-z: inherit;
    color: var(--ink); background: var(--cell); user-select: none;
    transform: rotate(var(--k-stamp-rot, -2deg)); animation: none; transition: none;
  }
  .k-live > .k-layer.k-stamp { transform: translateZ(calc(24px * var(--k-z, 0))) rotate(var(--k-stamp-rot, -2deg)); }
  .k-stamped > .k-layer.k-stamp { visibility: visible; }
  /* misstryck: the plate slipped. Two px off, faint, under the real print. */
  .k-layer.k-stamp.k-ghost {
    z-index: 1; opacity: 0.35; background: transparent;
    transform: translate(2px, 2px) rotate(var(--k-stamp-rot, -2deg));
  }
  .k-live > .k-layer.k-stamp.k-ghost { transform: translate3d(2px, 2px, calc(23px * var(--k-z, 0))) rotate(var(--k-stamp-rot, -2deg)); }

  /* ---- pressed: the JS class stands in for :active, which iOS does not set reliably (WebKit 221366) */
  :is([data-fx~="keycap"], [data-fx~="stamp"]).k-pressed { border-color: var(--ink-dim); }
  :is([data-fx~="keycap"], [data-fx~="stamp"]).k-pressed .tk-card-go { background: var(--ink-dim); }
}
/* Kortlek flow modules: field, wear, hum (enter is Web Animations only and needs no CSS).
   Own classes: k-near, k-word-near, k-nick, k-wear-word, k-hum, k-hum-layer, k-nat20, k-mythic. Never writes transform or
   box-shadow on the card, and never draws on the card's own ::before/::after (a hub skin owns those). */

@layer kortlek {

  /* ---- a state is a WORD (a .k-word span, type in base.css), never colour alone. Kept under reduced motion and forced colors.
     field: a displaced neighbour says [near]; wear: a worn card says [wear n]. Both sit after the arrow, which never moves. */
  .k-near .k-word-near, .k-word.k-wear-word { display: inline-flex; color: var(--ink-dim); }

  /* Tamed recipe, no script: while a card is awake the others step back 2px, like tvOS focus. It writes --k-back, a variable
     no spring owns (springs write --k-z inline and would beat a stylesheet). The attribute may sit on any ancestor of the cards.
     Not under reduced motion; :has() missing -> the block is skipped. */
  @supports selector(:has(*)) {
    @media (prefers-reduced-motion: no-preference) {
      :root:not([data-k-motion="off"]) [data-recipe~="trappa"]:has(.is-awake) :is(.k-card, [data-fx]):not(.is-awake) { --k-back: 2; }
    }
  }

  /* ---- wear: a worn card is looser. The tier is written by the module as data-k-wear. */
  [data-k-wear="2"] { --k-edge: 5; --k-spring-c: 20; }
  [data-k-wear="3"] { --k-edge: 5; --k-spring-c: 19; --k-tilt: 9deg; }
  [data-k-wear="4"] { --k-edge: 6; --k-spring-c: 17; --k-tilt: 10deg; }

  /* Nicks: one 4x4 square per tier in ink-dim at the state row's bottom-right corner, a single box-shadow list on one pseudo-element. */
  .k-layer.k-nick { z-index: 2; }
  .k-nick::before { content: ""; position: absolute; bottom: 8px; right: 8px; width: 4px; height: 4px; background: var(--ink-dim); }   /* the state row's free corner, clear of the strip and the words */
  [data-k-wear="0"] > .k-nick::before { display: none; }
  [data-k-wear="2"] > .k-nick::before { box-shadow: -7px 0 0 var(--ink-dim); }
  [data-k-wear="3"] > .k-nick::before { box-shadow: -7px 0 0 var(--ink-dim), -14px 0 0 var(--ink-dim); }
  [data-k-wear="4"] > .k-nick::before { box-shadow: -7px 0 0 var(--ink-dim), -14px 0 0 var(--ink-dim), -21px 0 0 var(--ink-dim); }

  /* The rare moments draw on the nick layer's ::after (an aria-hidden .k-layer child), never on the card's own pseudo-elements.
     nat20 (rare, 1 in 40 releases): a 1px inner outline steps in three times in 520 ms (= src/fx-flow.js). Small area. */
  .k-nat20 > .k-nick::after, .k-mythic > .k-nick::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    outline: 1px solid var(--ink-dim); outline-offset: -4px;
  }
  .k-nat20 > .k-nick::after { animation: k-nat20 520ms steps(1, end) both; }
  @keyframes k-nat20 { 0% { opacity: 0; } 25% { opacity: 1; } 60% { opacity: .4; } 100% { opacity: 0; } }

  /* mythic (the 100th visit, never explained): the same outline walks outward in 2px steps, 800 ms (= src/fx-flow.js),
     three transitions. It wins over a nat20 that lands at the same moment. */
  .k-mythic > .k-nick::after { outline-offset: -8px; animation: k-mythic 800ms steps(1, end) both; }
  @keyframes k-mythic {
    0% { opacity: 0; outline-offset: -8px; }
    34% { opacity: 1; outline-offset: -6px; }
    67% { opacity: .6; outline-offset: -4px; }
    100% { opacity: 0; outline-offset: -2px; }
  }

  /* ---- hum: a 1px inner line breathes in opacity only (compositor), while .k-hum is on. --k-hum scales it, 0 = never. */
  .k-layer.k-hum-layer {
    inset: 3px; z-index: 2; border: 1px solid var(--e1);
    opacity: 0; transition: opacity 120ms linear;
  }
  .k-hum > .k-hum-layer { animation: k-hum 2.4s ease-in-out 2 forwards; }   /* two breaths, 4.8 s: a loop never outlasts 5 s (WCAG 2.2.2) */
  @keyframes k-hum {
    0%, 100% { opacity: calc(.35 * var(--k-hum, 1)); }
    50% { opacity: calc(.9 * var(--k-hum, 1)); }
  }

  /* ---- reduced motion: no loop, no stepping. The lit state still shows, held still. */
  @media (prefers-reduced-motion: reduce) {
    .k-hum > .k-hum-layer { animation: none; opacity: calc(.6 * var(--k-hum, 1)); }
    .k-nat20 > .k-nick::after, .k-mythic > .k-nick::after { animation: none; opacity: 1; }
  }
  :root[data-k-motion="off"] .k-hum > .k-hum-layer { animation: none; opacity: calc(.6 * var(--k-hum, 1)); }
  :root[data-k-motion="off"] .k-nat20 > .k-nick::after,
  :root[data-k-motion="off"] .k-mythic > .k-nick::after { animation: none; opacity: 1; }
}
/* Kortlek fx-patrick: the CSS side of the cat (src/fx-patrick.js). The floor is a child of the card's strip: aria-hidden, no
   pointer events, nothing on the card itself. Colours are Navet's tokens only. */
@layer kortlek {
  :root { --k-sunlight: 1; }   /* strength of the sun on his floor, 0 = none */

  /* his floor is one text row (20px) taller than Navet's strip: his tallest frame, 72px, fits. min-height, so a host
     that sets the strip's height itself still gives way to it. */
  .k-pat .tk-strip { min-height: var(--k-strip-h, 76px); }
  .k-pat .tk-strip > * { visibility: hidden; }

  /* the floor is a layer of the card laid over the strip's box. It stays in the card's plane: a step forward in 3D would
     scale his pixels by a fraction, and his pixels are whole (Patrick's law 7) */
  .k-layer.k-pat-floor { inset: 0 0 auto 0; }
  .k-pat-sun, .k-pat-cat { position: absolute; bottom: 0; image-rendering: pixelated; image-rendering: crisp-edges; }
  .k-pat-sun { left: 0; transform: translate(calc(var(--k-sun, 0) * 1px), 0); }
  @supports (width: round(1px, 2px)) { .k-pat-sun { transform: translate(round(calc(var(--k-sun, 0) * 1px), 2px), 0); } }   /* the dots step by a dot, they never glide */
  .k-pat-cat { right: var(--q3, 12px); transform: translate(calc(var(--k-cat-x, 0) * 1px), 0); }   /* a walk is whole-pixel steps, never eased; flat, no layer at rest */
  .k-pat .k-stamp { inset: 8px auto auto 8px; }   /* the print goes to the empty end of his floor, not over his head */
  .is-awake .k-pat-sun { will-change: transform; }

  /* the only word he says: left of his head, at his own 11px, for as long as the mouth is open */
  .k-pat-mjau {
    position: absolute; right: calc(var(--q3, 12px) + 62px); bottom: 50px;
    line-height: 12px; color: var(--ink);   /* the rest of its type is the state word's, in base.css */ white-space: nowrap; visibility: hidden;
  }
  .k-pat-mjau.is-on { visibility: visible; }

  @media (forced-colors: active) { .k-pat-sun { display: none; } }
}
/* Recipes: a name for a set of knob values. The module list lives in data-fx on the cards (K.recipes in src/recipes.js);
   the numbers live here, so a page can restyle a recipe without touching script. Unlayered rules beat these. */
@layer kortlek {
  [data-recipe="quiet"]      { --k-sink: 0px; }   /* Navet's press today: the border, no travel below rest */
  [data-recipe="tactile"]    { --k-sink: 1px; --k-act: 2; --k-reset: 1.5; --k-t-attack: 90ms; --k-rebound: .5; }
  [data-recipe="linear"]     { --k-act: 2; --k-reset: 2; --k-t-attack: 140ms; --k-rebound: 1; --k-velocity: 0; --k-t-hold: 0ms; }
  [data-recipe="clicky"]     { --k-act: 2.5; --k-reset: 1; --k-t-attack: 60ms; --k-rebound: .3; --k-velocity: 1.6; --k-t-hold: 1500ms; --k-sink: 2px; }
  [data-recipe="instrument"] { --k-sheen: .5; --k-hum: 1; --k-t-hold: 1500ms; --k-act: 2; --k-reset: 1.5; }
  [data-recipe="foil"]       { --k-foil: .45; --k-sheen: .3; --k-tilt: 11deg; --k-spring-c: 20; }
  [data-recipe="membran"]    { --k-push: 8px; --k-quant: 2px; --k-reach: 2.2; --k-t-wave: 70ms; --k-sink: 1px; }
  [data-recipe="veteran"]    { --k-wear-rate: 3; --k-sink: 1px; }
  [data-recipe="stillbild"]  { --k-lift: 1px; --k-edge: 2; --k-sink: 0px; }   /* lift only: nothing to switch off */


  /* patrick: a row recipe for the one card Patrick lives on (src/fx-patrick.js). Critically damped and slower, no tilt (his pixels stay whole), a key
     that comes back without a bounce, no spotlight; the stepped edge in his coat's colours. */
  [data-recipe="patrick"] { --k-spring-k: 110; --k-spring-c: 21; --k-tilt: 0deg; --k-edge: 5; --k-sink: 1px; --k-t-attack: 140ms; --k-rebound: 1; --k-velocity: 0; --k-t-hold: 0ms; --k-sheen: 0; }
  [data-recipe="patrick"]:is(.k-card, [data-fx]), [data-recipe="patrick"] :is(.k-card, [data-fx]) {
    --e1: var(--gin-1); --e2: var(--gin-2); --e3: var(--gin-3); --e4: var(--gin-4); --e5: var(--gin-5); --e6: var(--gin-5);
  }
}
